localproxy — Anleitung & Tipps

localproxy ist ein schlanker lokaler HTTP-Proxy für Browser-basierte Online-Tools — Download, Start, Konfiguration und Endpunkte, um externe URLs ohne CORS-Probleme abzurufen.

localproxy ist ein schlanker, sicherer lokaler HTTP-Proxy für Browser-basierte Online-Tools. Er bindet ausschließlich an 127.0.0.1 und erlaubt deinen Web-Anwendungen, externe URLs abzurufen — ohne an den CORS-Beschränkungen des Browsers zu scheitern.

Anleitung

Voraussetzungen

  • Eine vorgefertigte Binary für deine Plattform (Linux, macOS, Windows, FreeBSD, OpenBSD, NetBSD — jeweils gängige Architekturen)
  • Alternativ für den Build aus dem Quellcode: Go 1.26+

Binary herunterladen

Vorgefertigte Binaries für alle Plattformen liegen im Repository unter Releases (z. B. localproxy-linux-amd64, localproxy-macos-apple-silicon, localproxy-windows-amd64.exe).

Ausführbar machen (macOS / Linux)

chmod +x ./localproxy-macos-apple-silicon
# macOS: Gatekeeper-Warnung der unsignierten Binary aufheben:
xattr -dr com.apple.quarantine ./localproxy-macos-apple-silicon

Starten

Im einfachsten Fall ohne weitere Konfiguration:

./localproxy

Beim Start zeigt ein Banner die wichtigen Werte an: die Adresse (http://127.0.0.1:PORT — ohne --port ein zufälliger freier Port), das bei jedem Start neu erzeugte Token, die verwendeten DNS-Server und die zugelassenen Origins.

Konfiguration

--port            int     TCP-Port (0 = zufälliger freier Port)                  [Standard: 0]
--origin          string  Erlaubte Origins, kommagetrennt
--timeout         int     Timeout für Upstream-Anfragen in Sekunden              [Standard: 30]
--max-mb          int64   Maximale Antwortgröße in MB (0 = unbegrenzt)           [Standard: 50]
--dns             string  DNS-Server, kommagetrennt, oder "system" für OS-DNS    [Standard: "1.1.1.1,8.8.8.8"]
--allow-any-host          Nicht-lokale Host-Header akzeptieren (nicht empfohlen)
--version                 Versionsinformation ausgeben und beenden

--port, --timeout und --max-mb werden beim Start validiert — ein Wert außerhalb des zulässigen Bereichs bricht mit einer Meldung ab, statt später verwirrend fehlzuschlagen.

Beispiele:

# Produktion: nur ein bestimmtes Tool zulassen
./localproxy --origin https://deintool.example.com

# Eigene DNS-Server (Quad9 + Cloudflare)
./localproxy --dns "9.9.9.9,1.1.1.1"

# DNS-Resolver des Betriebssystems verwenden
./localproxy --dns system

Standardmäßig löst localproxy Namen über Cloudflare (1.1.1.1) und Google (8.8.8.8) auf — unabhängig von der DNS-Konfiguration des Hosts, was über Maschinen hinweg konsistente Ergebnisse liefert. Fehlt beim --dns-Wert der Port, wird :53 automatisch ergänzt.

--allow-any-host

Standardmäßig beantwortet localproxy nur Anfragen, deren Host-Header das Loopback-Interface benennt (127.0.0.0/8, localhost oder [::1], mit beliebigem Port). Das blockiert DNS-Rebinding: Ohne die Prüfung könnte eine Webseite einen von ihr kontrollierten Hostnamen auf 127.0.0.1 zeigen lassen und den Proxy aus deinem Browser heraus ansprechen.

--allow-any-host brauchst du nur, wenn du den Proxy unter einem anderen lokalen Namen erreichst — etwa über einen Container-Alias. Greif lieber zu --origin, statt die Prüfung abzuschalten.

Endpunkte

Endpunkt Auth Beschreibung
GET/POST/HEAD /proxy?url=... ja (X-Proxy-Token) Anfrage an die Ziel-URL weiterleiten (Body-Streaming, Metadaten-Header)
GET /inspect?url=... ja Verbindungs-Metadaten als JSON (SSL, Timing, IP, Header, http3); &body=1 inkl. Body
GET /page?url=... ja Vollständige Seitenanalyse: Redirect-Kette + Body + SSL + Timing + http3 in einem JSON
`OPTIONS /proxy /inspect /page`
GET /ping nein Health-Check (gibt localproxy ok zurück)
GET /version nein Versionsinfo als JSON

Einbindung im Tool

  1. Nutzer gibt Adresse (http://127.0.0.1:PORT) und Token im Online-Tool ein.
  2. Das Tool prüft die Verbindung über /ping.
  3. Alle weiteren Anfragen laufen über /proxy?url=... mit dem Header X-Proxy-Token. Beispiel:
const response = await fetch(
  `${PROXY_BASE}/proxy?url=${encodeURIComponent(targetUrl)}`,
  { headers: { "X-Proxy-Token": PROXY_TOKEN } }
);

Jede /proxy-Antwort liefert zusätzlich Upstream-Metadaten als Header: X-Upstream-Protocol, X-Upstream-IP, X-Upstream-Timing (z. B. dns=12;tcp=45;ssl=23;ttfb=156;total=234), X-Upstream-Content-Encoding, X-Upstream-Content-Length und — nur wenn der Body die --max-mb-Grenze gerissen hat und abgeschnitten wurde — X-Upstream-Truncated: 1. Alle Upstream-Header sind per JavaScript lesbar.

Fehler sind auswertbar: Alle 4xx-Antworten tragen CORS-Header und einen JSON-Body mit maschinenlesbarem Code (bad_request, forbidden, blocked_target, method_not_allowed) — ein falsches Token ist damit von einem nicht erreichbaren Proxy unterscheidbar. Nur abgelehnte Origins bekommen bewusst ein nacktes 403 ohne CORS-Header.

Diese Header gehen nicht ans Ziel: Cookie, Cookie2, Authorization, Proxy-Authorization, Origin, Referer und Sec-Fetch-* werden nie weitergereicht, damit Anmeldedaten und Identitätssignale des Aufrufers nicht beim Zielserver landen. Header, die du bewusst setzt — Accept, User-Agent, Accept-Language, eigene X-* — passieren weiterhin.

Tipps & Tricks

  • In Produktion Origins einschränken: Ohne --origin sind alle Origins erlaubt. Für den produktiven Einsatz immer das konkrete Tool per --origin https://deintool.example.com freigeben.
  • HTTPS-Seite ruft HTTP-localhost auf — kein Problem: Browser behandeln http://127.0.0.1 als „potentially trustworthy origin" (W3C Secure Contexts). Anfragen von einer HTTPS-Seite an den lokalen Proxy werden nicht als Mixed Content blockiert; Chromes Private-Network-Access-Preflight wird automatisch beantwortet.
  • /page spart Roundtrips: Der Endpunkt verfolgt die Redirect-Kette (bis zu 20 Hops), lädt den Body, inspiziert SSL und misst Timings in einem einzigen JSON — er ersetzt mehrere /proxy- und /inspect-Aufrufe. Seit 1.1.0 leitet er sein gesamtes Zeitbudget aus dem Request-Kontext des Clients ab: Bricht der Client ab, endet auch die Arbeit am Server. Vorher liefen Redirect-Hops und Encoding-Proben mit context.Background(), sodass 20 langsame Redirects einen Handler minutenlang am Leben halten konnten.
  • SSL-Details, die der Browser nicht hergibt: /inspect liefert Zertifikatsinformationen (Version, Aussteller, Gültigkeit, SANs, Chain — inklusive IP-SANs), die über die Fetch-API nicht zugänglich sind. Ohne body=1 wird upstream nur ein HEAD-Request gesendet (schneller, kein Body-Download).
  • HTTP/3 erkennen, ohne HTTP/3 zu sprechen: /inspect und /page liefern seit Version 1.1.0 ein Feld http3. Es beantwortet „unterstützt dieses Ziel HTTP/3?" — nicht „lief diese Anfrage über HTTP/3?". Die Antwort stammt aus dem Alt-Svc-Feld der Zielantwort (RFC 7838, ALPN-Token h3 nach RFC 9114); genau so entdecken Browser HTTP/3 auch, nämlich über TCP. Es ist also kein QUIC-Stack im Spiel. Entwurfs-Token wie h3-29 werden ignoriert, das rohe Alt-Svc-Feld bleibt in der headers-Map verfügbar. protocol sagt weiterhin, worüber diese eine Anfrage tatsächlich lief — die Kombination {"protocol": "HTTP/2.0", "http3": true} ist der Normalfall, kein Widerspruch.
  • Sicherheits-Defaults: 48-Zeichen-Session-Token bei jedem Start neu, SSRF-Schutz, TLS-Upstream-Verifikation immer aktiv, Redirects werden nicht automatisch gefolgt (der Client entscheidet), Antwortgröße per --max-mb gedeckelt, Token-Vergleich in konstanter Zeit, X-Content-Type-Options: nosniff auf allen selbst erzeugten Antworten.
  • SSRF-Schutz greift erst im Dialer: Seit 1.1.0 wird die Zieladresse nach der DNS-Auflösung und vor dem Verbindungsaufbau geprüft. Die frühere Vorab-Prüfung löste den Namen getrennt vom HTTP-Client auf — eine vom Angreifer kontrollierte Zone mit kurzer TTL konnte der Prüfung eine öffentliche und der Verbindung eine Loopback-Adresse liefern. Die Vorab-Prüfung bleibt als schneller Abbruchpfad erhalten und ist auf 5 s begrenzt. Die Sperrliste deckt jetzt auch reservierten IPv4-Raum (0/8, 192.0.0.0/24, 198.18/15, Multicast, 240/4) und die IPv6-Formen mit eingebetteter IPv4-Adresse ab — IPv4-mapped, NAT64 (64:ff9b::/96) und 6to4 (2002::/16). 64:ff9b::7f00:1 erreicht in einem NAT64-Netz 127.0.0.1 und war einer rein v4-basierten Prüfung vorher unsichtbar.
  • Nebenläufigkeit & Kompression: Der Proxy bearbeitet Anfragen parallel (eine Goroutine pro Verbindung); jede Upstream-Anfrage nutzt eine frische Verbindung für genaues Timing. Brotli, gzip und deflate werden bei /proxy transparent durchgereicht — die Dekompression übernimmt der Browser. Bei /inspect mit body=1 dekomprimiert der Proxy selbst; Content-Encoding: deflate versteht seit 1.1.0 sowohl zlib-verpackte Daten (RFC 1950) als auch rohes DEFLATE (RFC 1951) — vorher kam die zlib-Variante, die ein großer Teil der Server sendet, leer oder beschädigt zurück.
  • Aus dem Quellcode bauen: Mit Go 1.26+ via go build -o localproxy . (die Releases werden mit Go 1.26.5 gebaut); Cross-Compiles über GOOS/GOARCH (z. B. GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -trimpath -o localproxy.exe .). Ein Git-Tag (git tag v1.1.0 && git push origin v1.1.0) stößt über GitHub Actions automatisch plattformübergreifende Release-Builds an.
  • Tests & Linting: go test -race ./... für den Race-Detector; das Projekt wird mit staticcheck gelintet, dazu kommen govulncheck und eine gofmt-Prüfung (CI blockt den Release bei jedem Befund). Seit 1.1.0 läuft die CI bei jedem Push und Pull Request, nicht mehr nur auf Tags — ein kaputter Test fällt damit nicht erst zum Release auf. staticcheck ist reines Dev-Tooling und landet nie in der Binary.

Weiterführende Informationen