
Eine Egress-Firewall für nicht vertrauenswürdige Workloads.
CI-Jobs, KI-Codierungsagenten und sandboxed Container können beliebige ausgehende Anfragen stellen. Eine kompromittierte Abhängigkeit, eine Prompt-Injection oder ein bösartiger Build- Schritt kann Geheimnisse exfiltrieren, sich nach Hause telefonieren oder eine Reverse Shell öffnen. Die meisten Teams haben keinerlei Sicht darauf, was ihre Workloads verlässt, geschweige denn eine Möglichkeit, es zu stoppen.
iron-proxy ist ein MITM-Egress-Proxy mit integriertem DNS-Server, der zwischen deiner nicht vertrauenswürdigen Workload und dem Internet sitzt. Er erzwingt Default-Deny an der Netzwerkgrenze, sodass die Workload nur Domains erreichen kann, die du explizit erlaubst. Echte Geheimnisse gelangen nie in die Sandbox. Workloads verwenden Proxy-Tokens, und iron-proxy tauscht am Egress echte Anmeldedaten ein. Das bedeutet, dass eine kompromittierte Workload ein Token exfiltrieren kann, das außerhalb des Proxys wertlos ist.
Ein einzelnes Binary. Eine einzelne YAML-Konfiguration.
169.254.169.254,
fd00:ec2::254 und fd20:ce::254) und Loopback sind standardmäßig gesperrt;
überschreibbar über proxy.upstream_deny_cidrs oder
IRON_PROXY_UPSTREAM_DENY_CIDRS.HTTP_PROXY, HTTPS_PROXY- oder SOCKS5-
Einstellungen unterstützen.SET ROLE auf der Upstream-
Sitzung injiziert und Client-Versuche ablehnt, die Rolle zu ändern (SET ROLE,
set_config('role', ...), DO-Blöcke usw.) durch einen SQL-AST-Durchlauf. Kombiniert mit
PostgreSQL Row-Level Security ergibt das eine Mandanten-Isolation, wenn
die Anwendung als gemeinsamer Dienstkonto-Benutzer verbunden ist. Erfordert
PgBouncer (falls verwendet) im Modus pool_mode = session — Transaktions- oder
Statement-Pool-Modi binden Backends zwischen Abfragen stillschweigend neu und würden
die Richtlinie aushebeln. Siehe docs.iron.sh für Details.Gebaut für CI-Pipelines, GitHub Actions, KI-Agenten (Claude Code, Cursor, Codex) und jede Umgebung, in der du Code ausführst, dem du nicht vollständig vertraust.
Docker-Images sind auf Docker Hub verfügbar und vorgefertigte Binaries für Linux/macOS (amd64/arm64) gibt es auf GitHub Releases.
Oder aus dem Quellcode bauen:```bash go build -o iron-proxy ./cmd/iron-proxy
## Schnellstart```bash
cd examples/docker-compose
docker compose up
Dies startet iron-proxy und einen Demo-Client, der fünf Anfragen über den Proxy sendet. Prüfen Sie die Logs, um erlaubte, blockierte und geheim-umgeschriebene Anfragen zu sehen:```bash docker compose logs proxy
Jede Anfrage erzeugt einen strukturierten JSON-Auditeintrag:```json
{
"host": "httpbin.org",
"method": "GET",
"path": "/headers",
"action": "allow",
"status_code": 200,
"duration_ms": 142,
"request_transforms": [
{ "name": "allowlist", "action": "continue" },
{
"name": "secrets",
"action": "continue",
"annotations": { "swapped": [{ "secret": "OPENAI_API_KEY", "locations": ["header:Authorization"] }] }
}
]
}
Abgelehnte Anfragen enthalten ein Feld rejected_by und werden auf WARN-Ebene protokolliert. Siehe
Audit-Log-Format für das vollständige Schema.
iron-proxy beendet TLS, indem es spontan Blattzertifikate erzeugt, die von einer CA signiert werden, die Sie bereitstellen. Client-Container müssen dieser CA vertrauen.```bash
mkdir -p certs
openssl genrsa -out certs/ca.key 4096
openssl req -x509 -new -nodes
-key certs/ca.key
-sha256 -days 3650
-subj "/CN=iron-proxy CA"
-addext "basicConstraints=critical,CA:TRUE"
-addext "keyUsage=critical,keyCertSign"
-out certs/ca.crt
### 2. Ein Docker-Netzwerk erstellen
iron-proxy benötigt eine feste IP-Adresse, damit Container ihre DNS darauf ausrichten können:```bash
docker network create --subnet=172.20.0.0/24 iron-proxy
Erstellen Sie eine Env-Datei mit Ihren Geheimnissen (bewahren Sie diese außerhalb der Versionskontrolle auf):```bash echo "OPENAI_API_KEY=sk-real-key" > .env
I don't see any content after "INPUT:" in your message — the chunk to translate appears to be missing. Please re-send chunk 15 with the actual Markdown content included, and I'll translate it into German right away.```bash
docker run -d --name iron-proxy \
--network iron-proxy --ip 172.20.0.2 \
-v $(pwd)/proxy.yaml:/etc/iron-proxy/proxy.yaml:ro \
-v $(pwd)/certs/ca.crt:/etc/iron-proxy/ca.crt:ro \
-v $(pwd)/certs/ca.key:/etc/iron-proxy/ca.key:ro \
--env-file .env \
ironsh/iron-proxy:latest -config /etc/iron-proxy/proxy.yaml
Der einfachste Ansatz ist DNS-basiertes Routing: Zeigen Sie das DNS des Containers auf
iron-proxy und alle Hostname-Auflösungen werden auf die Proxy-IP aufgelöst, wodurch der Datenverkehr
automatisch durch ihn geleitet wird:```bash
docker run --rm
--network iron-proxy
--dns 172.20.0.2
-v $(pwd)/certs/ca.crt:/certs/ca.crt:ro
curlimages/curl --cacert /certs/ca.crt https://httpbin.org/get
Für eine strengere Durchsetzung können Sie nftables-Regeln schichten, um Non-Proxy-Ausgangsverkehr zu blockieren, oder verwenden Sie
TPROXY für das Abfangen auf Kernel-Ebene. Siehe [Routing traffic to the
proxy](#routing-traffic-to-the-proxy) für Details zu jedem Ansatz.
## Warum iron-proxy?
| | iron-proxy | Squid | mitmproxy | Envoy |
| ------------------------ | ------------------------------ | --------------------------- | ------------------------- | ---------------------------------- |
| Default-Deny-Egress | Integriert | Erfordert komplexe ACL-Konfiguration | Erfordert benutzerdefiniertes Scripting | Erfordert RBAC-/Filterkonfiguration |
| Secret-Injection | Integriert | Nein | Nein | Nein |
| Strukturierte Audit-Protokollierung | Integriert, Traces pro Transformationsschritt | Einfache Zugriffsprotokolle | Plugin-basiert | Konfigurierbare Zugriffsprotokolle |
| Einrichtungskomplexität | Einzelne Binärdatei + YAML | Umfangreiche Konfigurationssprache | Python-Scripting | Komplexes YAML oder Control Plane |
iron-proxy ist gezielt für eine Aufgabe entwickelt: die Kontrolle und Überwachung des ausgehenden Datenverkehrs (Egress) von nicht vertrauenswürdigen Workloads. Squid kann Default-Deny umsetzen, erfordert jedoch erhebliche ACL-Konfiguration und hat kein Konzept für Secret Injection. mitmproxy ist ein großartiges Debugging-Tool, aber nicht für die Durchsetzung im Produktionsbetrieb ausgelegt. Envoy ist ein Allzweck-Proxy, der so konfiguriert werden kann, dass er Teile davon übernimmt, aber das ist weitaus komplexer, als das Problem es erfordert.
## Wie es funktioniert
iron-proxy betreibt einen DNS-Server und einen HTTP/HTTPS-Proxy. Richten Sie den DNS Ihres Containers auf iron-proxy aus, und alle Hostnamen-Auflösungen werden auf die Proxy-IP aufgelöst, wodurch der Datenverkehr automatisch über den Proxy geleitet wird. Der Proxy beendet TLS (indem er spontan Blattzertifikate aus einer von Ihnen bereitgestellten CA erzeugt), führt die Anfrage durch eine geordnete Transformations-Pipeline, leitet sie an den Upstream weiter und führt die Antwort wieder durch die Pipeline.```
Container → DNS lookup → iron-proxy IP → TLS termination → transforms → upstream
Transforms laufen in Reihenfolge. Eingebaute Transforms:
| Transform | Was er tut |
|---|---|
allowlist | Erlaubt Anfragen an passende Domains/CIDRs; lehnt alles andere ab (403). |
secrets | Durchsucht Header (und optional Query, Pfad oder Body) nach Proxy-Tokens und tauscht echte Secrets aus Umgebungsvariablen ein. |
iron-proxy akzeptiert ein einziges Flag: -config path/to/config.yaml. Hier ist die
vollständige Form (siehe iron-proxy.example.yaml für einen
kopierbaren Ausgangspunkt):```yaml
dns:
listen: ":53"
proxy_ip: "10.16.0.1" # IP where iron-proxy is running (required)
passthrough: # Domains forwarded to OS resolver
- "*.internal.corp"
- "metadata.google.internal"
records: # Static DNS records (highest precedence)
- name: "internal.example.com"
type: A
value: "10.0.0.5"
proxy: http_listen: ":80" https_listen: ":443" tunnel_listen: ":8080" # Optional CONNECT/SOCKS5 listener max_request_body_bytes: 1048576 # 1 MiB (default) max_response_body_bytes: 0 # uncapped (default)
tls: ca_cert: "/etc/iron-proxy/ca.crt" # Required ca_key: "/etc/iron-proxy/ca.key" # Required cert_cache_size: 1000 # LRU cache for generated leaf certs leaf_cert_expiry_hours: 72
transforms:
name: allowlist config: domains: - "api.openai.com" - "*.anthropic.com" cidrs: - "10.0.0.0/8"
name: secrets config: secrets: - source: type: env var: OPENAI_API_KEY # Env var holding the real secret proxy_value: "proxy-token-123" # Token the sandbox sends match_headers: ["Authorization"] match_body: false require: true # Reject requests without the proxy token rules: - host: "api.openai.com"
log: level: "info" # debug, info, warn, error
### DNS
Standardmäßig wird alles zu `proxy_ip` aufgelöst, wodurch der Datenverkehr durch
den Proxy geleitet wird. Ausnahmen:
- **`passthrough`:** Glob-Muster, die an den OS-Resolver weitergeleitet werden (z. B.
`*.internal.corp`). Datenverkehr zu diesen Hosts umgeht den Proxy vollständig.
- **`records`:** statische A- oder CNAME-Einträge. Höchste Priorität.
### Response-Retry-Handler
Setzen Sie `IRON_RESPONSE_RETRY_HANDLER_URL`,
`IRON_RESPONSE_RETRY_COMPLETE_URL`, `IRON_RESPONSE_RETRY_HANDLER_TOKEN`,
`IRON_RESPONSE_RETRY_HANDLER_SANDBOX_ID` sowie eine durch Kommas getrennte
`IRON_RESPONSE_RETRY_STATUSES`-Liste, um extern autorisierte Antwortwiederholungen
zu aktivieren. Der Autorisierungshandler empfängt das exakte Upstream-Schema, die Authority,
die Methode, den Pfad bzw. die Query, die Wiederholbarkeit, den Antwortstatus und die Antwortheader,
den Trace-Kontext sowie die Sandbox-Identität. Er kann Anforderungsheader plus eine Attempt-ID
für eine exakte Wiederholung zurückgeben. Der Abschlusshandler empfängt dann den
Wiederholungsstatus und die Antwortheader, die über `IRON_RESPONSE_RETRY_COMPLETION_HEADERS` ausgewählt werden;
standardmäßig ist dies `Payment-Receipt`.
Antwort-Bodies werden niemals an einen der beiden Handler gesendet, Ziele können nicht geändert werden,
und Verbindungs-/Framing-Header werden abgelehnt. Anforderungen über
`proxy.max_request_body_bytes` werden normal verarbeitet, aber als nicht wiederholbar markiert;
wenn eine Wiederholung angefordert wird, wird ihre ursprüngliche Antwort zurückgegeben. Auch bei
Handler-Fehlern bleibt die ursprüngliche Antwort erhalten. Handler-URLs müssen HTTPS verwenden, sofern es
sich nicht um Loopback handelt oder `IRON_RESPONSE_RETRY_HANDLER_ALLOW_HTTP=true` explizit für ein
vertrauenswürdiges internes Netzwerk konfiguriert ist. Weiterleitungen werden abgelehnt, und das Response-Retry-Token
muss unabhängig vom Control-Plane-Token konfiguriert werden. WebSocket- und gRPC-Anforderungen sowie
Streaming-Anforderungen mit unbekannter Länge umgehen die Response-Retry-Verarbeitung.
Wenn ein vertrauenswürdiger Handler innerhalb von `proxy.upstream_deny_cidrs` aufgelöst wird, setzen
Sie `IRON_RESPONSE_RETRY_HANDLER_ALLOW_CIDRS` auf eine durch Kommas getrennte Liste der
engen privaten CIDRs, die er verwenden darf. Diese Ausnahme gilt nur für die exakt
konfigurierten Authorize- und Complete-Endpunkte; normaler weitergeleiteter Datenverkehr unterliegt
weiterhin der vollständigen Upstream-Deny-Liste. Öffentliche, Loopback-, Link-Local- und Cloud-Metadaten-Bereiche
können über diese Einstellung nicht hinzugefügt werden.
### Allowlist
Default-Deny. Anforderungen müssen mindestens einem Domain-Glob oder einer CIDR entsprechen, um fortzufahren.
Nicht übereinstimmende Anforderungen erhalten ein `403 Forbidden`.
Domain-Muster verwenden Glob-Matching: `*.example.com` entspricht jeder Subdomain und
`example.com` selbst.
**Warnmodus:** Setzen Sie `warn: true`, um zu beobachten, was die Allowlist blockieren würde, ohne
sie tatsächlich durchzusetzen. Anforderungen, die abgelehnt würden, werden durchgelassen, aber
in der Transform-Trace mit `"action": "warn"` annotiert. Dies ist nützlich, um
neue Allowlist-Regeln einzuführen oder bestehenden Datenverkehr zu prüfen, bevor auf
Durchsetzung umgestellt wird.
### Annotieren
Erfasst HTTP-Anforderungsheader in Audit-Log-Annotationen basierend auf
Host-/Methoden-/Pfad-Regeln. Dies ist nützlich, um Audit-Logs mit
anforderungsspezifischem Kontext wie Request-IDs anzureichern, ohne den Proxy-Kern zu verändern.
Jede Annotationsgruppe gibt abzugleichende Regeln und zu erfassende Header an. Wenn eine
Anforderung einer beliebigen Regel in einer Gruppe entspricht, werden die angegebenen Header-Werte als
`header:<Name>`-Einträge in die Transform-Trace-Annotationen geschrieben. Nicht übereinstimmende
Anforderungen werden unverändert durchgereicht. Diese Transformation lehnt niemals Anforderungen ab.
> **Warnung:** Header-Werte werden im Klartext im Audit-Log ausgegeben. Protokollieren Sie
> nur Header, die sicher offengelegt werden können, wie Request-IDs oder Header, die
> Proxy-Secret-Tokens enthalten. Protokollieren Sie keine Header, die rohe Geheimnisse enthalten.```yaml
transforms:
- name: annotate
config:
annotations:
- rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
headers: ["x-request-id"]
- rules:
- host: "*.anthropic.com"
headers: ["x-request-id"]
Default-Deny-Request-Header-Filter. Jeder Request-Header, dessen kanonischer Name nicht in der konfigurierten headers-Liste enthalten ist, wird entfernt, bevor der Request an den Upstream gesendet wird. Nützlich zum Blockieren von Tracking-, Fingerprinting- oder versehentlichen Leckage-Headern (Cookies, interne Korrelations-IDs, X-Forwarded-* usw.), die die Sandbox möglicherweise anhängt.
Einträge werden case-insensitiv gegen den kanonischen Header-Namen abgeglichen. Muster, die durch /.../ begrenzt werden (z. B. /^X-Trace-.*$/), sind case-insensitive reguläre Ausdrücke und entsprechen der match_headers-Syntax des secrets-Transforms.
Optionale rules beschränken die Allowlist auf bestimmte Hosts/Methoden/Pfade. Wenn sie weggelassen werden, gilt die Allowlist für jeden Request, der diesen Transform erreicht.
Wenn mindestens ein Header entfernt wird, wird der Trace mit stripped_headers annotiert, das die entfernten Namen auflistet.
Platzierung: Setzen Sie
header_allowlistnachsecrets(damit injizierte Anmeldeinformationen nicht entfernt werden, falls sie nicht in der Allowlist stehen; Sie können sie dort auflisten) und nachannotate(damit die Annotation die ursprünglichen Header liest).```yaml transforms:
### Body-Erfassung
Zeichnet den dekodierten Request-Body übereinstimmender Anfragen auf und stellt ihn
im Audit-Log-Datensatz in einer `body_capture`-Gruppe bereit, die `request_body` und
`request_body_truncated` enthält. Nützlich, um die Payloads zu prüfen, die den
Proxy durchlaufen, z. B. die Prompts, die eine Sandbox an einen LLM-Anbieter sendet,
ohne den Upstream-Datenverkehr zu verändern.
Hosts, Methoden und Pfade werden mit derselben `rules`-Syntax wie `allowlist` und
`secrets` abgeglichen. `max_request_body_bytes` begrenzt, wie viel von jedem Body
erfasst wird; Bodies, die größer als die Grenze sind, werden auf das Präfix gekürzt
und `request_body_truncated` wird auf `true` gesetzt. Die Grenze beträgt standardmäßig
16 KiB und ist unabhängig von der globalen `proxy.max_request_body_bytes`-Begrenzung.
Diese Transformation ist rein beobachtend: Sie lehnt niemals eine Anfrage ab, und
Lesefehler des Bodys werden im Trace annotiert, anstatt die Anfrage fehlschlagen zu lassen.
Bei einer erfolgreichen Erfassung wird der Eintrag der Transformation in `request_transforms`
mit `captured_bytes` und `truncated` annotiert, sodass der Trace protokolliert, dass ein
Body erfasst wurde, ohne den Body selbst zu duplizieren.
Antwort-Bodies werden nicht erfasst. Streaming-Antworten (SSE) müssten vor dem
Weiterleiten vollständig gepuffert werden, was den Client aufhalten würde.
> **Warnung:** Erfasste Bodies werden im Klartext ins Audit-Log geschrieben. Wenn
> `secrets` mit `match_body: true` läuft, platziere `body_capture` *vor* `secrets`,
> damit das Audit-Log die Proxy-Tokens der Sandbox protokolliert statt der echten
> Anmeldedaten, die `secrets` in den Body einsetzt.```yaml
transforms:
- name: body_capture
config:
max_request_body_bytes: 16384
rules:
- host: "api.anthropic.com"
methods: ["POST"]
paths: ["/v1/messages"]
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/chat/completions"]
Die Sandbox enthält niemals echte Anmeldedaten. Stattdessen:
proxy-openai-abc123).secrets, um Proxy-Tokens diesen Quellen zuzuordnen.iron-proxy scannt ausgehende Anfragen und ersetzt Proxy-Tokens mit den echten Werten, bevor sie an den Upstream weitergeleitet werden. Sie kontrollieren, wo gesucht wird:
match_headers: Liste der zu durchsuchenden Header-Namen. Leere Liste = alle Header.
Wörtliche Namen werden case-insensitiv abgeglichen, aber die Schreibweise, die Sie angeben, wird
beibehalten, wenn der Header an den Upstream weitergeleitet wird. Einträge, die durch /.../
begrenzt sind, werden als case-insensitive reguläre Ausdrücke kompiliert, die gegen kanonische
Header-Namen abgeglichen werden (z. B. /^x-.*-key$/).match_body: scannt den Anforderungstext (bis zu max_request_body_bytes gepuffert).match_query: scannt die URL-Query-Strings. Standardmäßig false; aktivieren für
Upstreams, die das Secret in einem Query-Parameter erwarten. Query-Strings erscheinen häufig
in Zugriffsprotokollen auf beiden Seiten des Proxys, daher ist dies standardmäßig deaktiviert.match_path: scannt den URL-Pfad. Standardmäßig false; aktivieren für Upstreams
wie Telegram, die das Secret im Pfad einbetten (z. B.
/bot<TOKEN>/sendMessage). URL-Pfade erscheinen häufig in Zugriffsprotokollen auf beiden
Seiten des Proxys, daher ist dies standardmäßig deaktiviert.Query-Parameter werden immer gescannt.
Secret-Quellen:
env: liest var aus der Prozessumgebung des Proxys. Zum Prozessstart
fixiert — verwenden Sie stattdessen file, wenn Sie den Wert bei einem laufenden Proxy rotieren müssen.file: liest das Secret aus path auf der Festplatte. Die Datei wird bei jedem
Konfigurations-Reload (Boot und jedes POST /v1/reload) erneut gelesen und, wenn ttl gesetzt ist,
bei Cache-Ablauf — so können Sie das Secret eines laufenden Proxys rotieren, indem Sie die Datei
(atomar: temp schreiben + umbenennen) neu schreiben und neu laden, ohne einen Neustart. Der
Wert ist exakt der Dateiinhalt (kein Trimmen), daher kontrolliert der Schreibende
abschließende Leerzeichen. Optionale ttl und failure_ttl werden unterstützt.aws_sm: liest secret_id aus AWS Secrets Manager. Optionale region,
und werden unterstützt.Jede Quelle akzeptiert außerdem ein optionales json_key. Wenn gesetzt, wird der aufgelöste Wert
als JSON-Objekt geparst und das einzelne Top-Level-String-Feld unter diesem Schlüssel
extrahiert. Verwenden Sie dies, um ein Feld aus einem JSON-Secret herauszuziehen.
ttl steuert, wie lange ein erfolgreich abgerufener Wert zwischengespeichert wird, bevor er aktualisiert wird
(leer = für immer cachen). failure_ttl steuert, wie lange ein Abruffehler
zwischengespeichert wird, bevor er erneut versucht wird; es ist standardmäßig 1m und unabhängig von ttl,
sodass eine lange Erfolgs-TTL die Wiederherstellung nach einem vorübergehenden Backend-Ausfall nicht verzögert.
Hinweis: ein Fehler in
onepassword-sdk-goverhindert Builds mitCGO_ENABLED=0, daher pinnt iron-proxy einen Fork über einereplace-Direktive ingo.mod, bis der Fix upstream verfügbar ist.
Der Judge-Transform ruft ein LLM auf, um eine Erlauben/Ablehnen-Entscheidung für
Anfragen zu treffen, die seinen URL-Regeln entsprechen. Jeder Eintrag unter transforms: ist eine
unabhängige Judge-Instanz mit eigener Policy in natürlicher Sprache, eigenem LLM-Backend,
Timeout, Semaphor und Circuit Breaker. Betreiber können null, eine oder
mehrere Judges mit unterschiedlichen Prompts bereitstellen, die auf unterschiedliche Regeln ausgerichtet sind.```yaml
Invarianten:
- Der Judge kann nur ablehnen. Er genehmigt nie eine Anfrage, die die statische Allowlist abgelehnt hätte. Statisches Deny gewinnt immer.
- Nicht passende Anfragen werden ignoriert: kein LLM-Aufruf, keine Audit-Anmerkungen.
- Bei LLM-Fehler, Timeout, offenem Circuit Breaker oder fehlerhafter Modellausgabe greift das konfigurierte `fallback`. `deny` blockiert die Anfrage (die empfohlene Standardeinstellung für die Produktion). `skip` überlässt den Rest der Pipeline; da iron-proxy standardmäßig Deny ist, werden nicht passende Anfragen weiterhin blockiert.
Pipeline-Reihenfolge mit der Secrets-Transformation:
- **Empfohlen:** Platzieren Sie den Judge **vor** der Secrets-Transformation. Der LLM-Anbieter sieht Proxy-Tokens, niemals die echten Anmeldeinformationen, auf die die Workload Zugriff hat.
- Alternativ kann der Judge nach den Secrets die exakte Wire-Form bewerten, die das System verlässt, allerdings werden dann echte Anmeldeinformationen an den LLM-Anbieter gesendet. Wählen Sie dies nur, wenn Ihr Bedrohungsmodell diesen Kompromiss akzeptiert.
Unterstützte Anbieter:
- **`anthropic`** (Messages-API). Verwendet `api_key_env`, `model`, optional `base_url` und `max_tokens`.
- **`openai`** (Chat-Completions-API). Gleiche Felder wie oben; setzen Sie `type: openai`, zeigen Sie mit `api_key_env` auf die Umgebungsvariable, die Ihren OpenAI-Schlüssel enthält, und wählen Sie ein Modell wie `gpt-5.4-nano`.
Audit-Ausgabe: Jede passende Anfrage fügt strukturierte Felder unter der Transform-Trace hinzu, einschließlich `judge.instance`, `judge.decision`, `judge.reason`, `judge.duration_ms`, `judge.input_tokens`, `judge.output_tokens`, `judge.fallback_applied` (wenn ein Fallback ausgelöst wird) und `judge.circuit_breaker_tripped` (wenn der Circuit Breaker offen ist).
Danksagung: Dank an Brex für ihr CrabTrap-Projekt (MIT-lizenziert), das dieses Design inspiriert hat.
## MCP-Richtlinie
iron-proxy unterstützt den [Streamable-HTTP-Transport von MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports). Wenn eine Anfrage zu einem konfigurierten MCP-Server passt, parst der Proxy den JSON-RPC-Body, wendet eine Standard-Deny-Tool-Allowlist an und filtert `tools/list`-Antworten, sodass abgelehnte Tools den Agenten nie erreichen. SSE-Antworten werden pro Ereignis gefiltert, sodass langlebige MCP-Streams aktiv bleiben.
Dies ist eine vollwertige Proxy-Fähigkeit und keine Transformation: MCP-Antworten können offene SSE-Streams sein, die beliebige serverinitiierte Nachrichten übertragen, was nicht in den Request/Response-Transform-Vertrag passt.```yaml
mcp:
# JSON-RPC error envelope returned to the agent on policy denial.
# Defaults: code -32001, message "blocked by iron-proxy policy".
error:
code: -32001
message: "blocked by iron-proxy policy"
servers:
- name: github # appears in audit as mcp.server
rules: # standard host/method/path rules
- host: "mcp.github.com"
paths: ["/mcp", "/mcp/*"]
tools:
- name: "search_repositories" # always allowed
- name: "create_issue"
when: # all clauses must hold; otherwise deny
- path: "owner" # dotted path against arguments
equals: "ironsh"
- path: "repo"
in: ["iron-proxy", "tunis-v2"]
# Anything not listed is denied (default-deny).
Verhalten:
tools/call. Aufrufe von Tools, die nicht in der tools-Liste des Servers stehen oder deren arguments eine when-Klausel nicht erfüllen, werden abgelehnt, ohne den Upstream zu erreichen. Der Proxy gibt eine JSON-RPC-Fehlerantwort mit dem konfigurierten Code und der konfigurierten Meldung sowie der ursprünglichen id der Anfrage zurück, sodass der MCP-Client einen normalen Protokollfehler sieht und keinen HTTP-Fehler.tools/list. Antworten auf tools/list werden um alle Tools gekürzt, die nicht auf der Zulassungsliste stehen, bevor sie den Agenten erreichen. Funktioniert sowohl für application/json- als auch für text/event-stream-Antworten; die SSE-Filterung arbeitet pro Ereignis, sodass Heartbeats und andere Nachrichten im Stream unverändert durchlaufen.when-Klausel hat einen punktierten path (z. B. arguments.repo, ) und entweder (beliebiger JSON-Skalar), (eine Liste von Skalaren) oder (ein Regex auf Zeichenfolgenwerte). Klauseln werden UND-verknüpft. Wenn weggelassen wird, ist das Tool bedingungslos erlaubt.Pipeline-Reihenfolge: Der MCP-Interceptor läuft nach der Transform-Pipeline, sodass allowlist weiterhin bestimmt, welche Hosts erreicht werden können, und secrets die Proxy-Token bereits ausgetauscht hat, bevor der Interceptor den Body auswertet.
mcp_gateway leitet clientseitige MCP-Hosts an konkrete Upstream-Server weiter, nachdem die MCP-Richtlinie die Anfrage akzeptiert hat. Dadurch können Agenten stabile interne Hosts aufrufen, während iron-proxy an den echten Upstream weiterleitet und Anmeldedaten injiziert, die die Sandbox nie erreichen.
Gateway-Routen gelten nur für Anfragen, die einem MCP-Server zugeordnet wurden. Die MCP-Richtlinie erzwingt weiterhin zuerst die Tool-Zulassungsliste. Wenn die Richtlinie einen tools/call ablehnt, wird die Gateway-Route nicht angewendet und der Upstream nicht erreicht.```yaml
mcp:
servers:
- name: github
rules:
- host: "github.mcp.local"
paths: ["/mcp", "/mcp/*"]
tools:
- name: "search_repositories"
mcp_gateway: routes: - name: github rules: - host: "github.mcp.local" paths: ["/mcp", "/mcp/*"] upstream: "https://mcp.github.com/v1" credentials: - source: type: env var: GITHUB_MCP_TOKEN inject: header: Authorization formatter: "Bearer {{ .Value }}"
Credentials verwenden dieselben Secret-Quellen wie die `secrets`-Transformation. Sie sind standardmäßig erforderlich. Setzen Sie bei einem Credential `require: false`, um es zu überspringen, wenn es nicht verfügbar ist. Audit-Logs zeichnen die Route, die Upstream-URL und die Orte der Credential-Injektion auf, niemals jedoch die injizierten Credential-Werte.
Einschränkungen in v1:
- Nur der Streamable-HTTP-Transport wird unterstützt. Der ältere HTTP+SSE-Transport (getrennte `/messages`- und `/sse`-Endpunkte) wird nicht unterstützt.
- Ein JSON-RPC-Batch mit einem verweigerten Eintrag wird als gesamter Batch abgelehnt; das Weiterleiten von Teil-Batches wird nicht unterstützt.
- Ressourcen und Prompts werden nicht erzwungen. Agents können weiterhin `resources/list`, `resources/read` usw. ohne Policy-Filter aufrufen.
### Body-Limits
Transformationen, die Anforderungs-/Antwort-Bodys untersuchen oder weiterleiten (Secrets-Body-Matching,
gRPC-Transformationen), arbeiten mit gepufferten Bodys. Zwei globale Einstellungen steuern
die maximalen Puffergrößen:
- **`max_request_body_bytes`** (Standard: `1048576` / 1 MiB): begrenzt, wie viel vom
Anforderungsbody für Transformationen gepuffert wird. Daten über dieses Limit hinaus werden
aus Sicht der Transformation abgeschnitten, aber weiterhin an Upstream weitergeleitet.
- **`max_response_body_bytes`** (Standard: `0` / unbegrenzt): begrenzt, wie viel vom
Antwortbody gepuffert wird. Setzen Sie den Wert auf `0`, um den vollständigen Body zu puffern –
die richtige Standardeinstellung für die meisten Arbeitslasten (z. B. npm-Pakete, Modellgewichte).
Bodys werden inkrementell gepuffert, während Transformationen sie lesen, und zwischen
Pipeline-Stufen automatisch zurückgespult. Wenn eine Transformation den Body nicht liest, findet
keine Pufferung statt und der Body wird unverändert durchgestreamt.
### Tunnel-Listener (HTTP/CONNECT/SOCKS5)
Der Tunnel-Listener akzeptiert HTTP-Proxy-Anfragen in absoluter Form, HTTP-CONNECT-
und SOCKS5-Verbindungen auf einem dedizierten Port. Dies ist nützlich für Tools, die
Proxy-Konfiguration nativ über die Umgebungsvariablen `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`
oder SOCKS5-Einstellungen unterstützen, anstatt auf DNS-basiertes Routing
angewiesen zu sein.
Um ihn zu aktivieren, setzen Sie `tunnel_listen` unter `proxy`:```yaml
proxy:
tunnel_listen: ":8080"
Wenn ausgelassen, ist der Tunnel-Listener deaktiviert.
Alle Protokolle durchlaufen dieselbe Transform-Pipeline wie reguläre HTTP/HTTPS-Anfragen. HTTP-Anfragen in absoluter Form werden über den normalen HTTP-Proxy-Pfad verarbeitet. Für CONNECT und SOCKS5 wertet der Proxy eine synthetische CONNECT-Anfrage gegen deine Allowlist und Secrets-Transforms aus, sodass Tunnelverbindungen derselben Standard-Verweigerungsrichtlinie unterliegen.
Nach dem CONNECT- oder SOCKS5-Handshake prüft der Proxy das erste Byte, um das innere Protokoll zu erkennen:
HTTP-CONNECT-Beispiel:```bash
curl -x http://172.20.0.2:8080
--cacert /certs/ca.crt
https://httpbin.org/get
**Einfaches HTTP-Proxy-Beispiel:**```bash
curl -x http://172.20.0.2:8080 \
http://httpbin.org/get
SOCKS5-Beispiel:```bash
curl --socks5-hostname 172.20.0.2:8080
--cacert /certs/ca.crt
https://httpbin.org/get
Sie können auch die Standard-Umgebungsvariablen setzen, sodass alle Tools
automatisch durch den Tunnel geleitet werden:```bash
export HTTP_PROXY=http://172.20.0.2:8080
export HTTPS_PROXY=http://172.20.0.2:8080
export ALL_PROXY=socks5h://172.20.0.2:8080
Die SOCKS5-Implementierung unterstützt nur „no-auth" und akzeptiert IPv4-, IPv6- und Domainnamen-Adresstypen.
iron-proxy generiert Leaf-Zertifikate im laufenden Betrieb, die von der von Ihnen bereitgestellten CA signiert werden.
Der Client-Container muss dieser CA vertrauen (fügen Sie sie dem System-Vertrauensspeicher hinzu oder übergeben Sie sie
über --cacert). Zertifikate werden in einem LRU-Cache mit dem SNI-Hostnamen als Schlüssel zwischengespeichert.
Es gibt drei Ansätze mit zunehmender Durchsetzung.
Richten Sie die DNS-Auflösung des Containers auf iron-proxy aus. Alle Lookups lösen zur Proxy-IP auf, sodass HTTP/HTTPS-Datenverkehr auf natürliche Weise darüber fließt. Dies ist das, was das Docker-Compose-Beispiel verwendet:```yaml services: client: dns: - 172.20.0.2 # iron-proxy IP
Einfach einzurichten, aber leicht zu umgehen: Die Workload kann IPs hartkodieren oder ihren
eigenen DNS-Resolver verwenden, um den Proxy vollständig zu umgehen.
### DNS + nftables-Egress-Firewall (erzwungen)
Legen Sie eine nftables-Firewall über das DNS-Routing. DNS lenkt den Datenverkehr weiterhin zum
Proxy, aber nftables stellt sicher, dass die Workload _nicht_ mit irgendetwas anderem kommunizieren kann,
selbst mit hartkodierten IPs.
Das Verzeichnis [`examples/nftables`](https://github.com/paradigmxyz/iron-proxy/blob/HEAD/examples/nftables/) enthält ein funktionierendes Setup.
Der Client-Container lädt die Firewall-Regeln beim Start, bevor er
Anwendungsdatenverkehr ausführt:
**nftables.conf** erlaubt Datenverkehr zum Proxy und verwirft alles andere:```
table ip iron {
chain output {
type filter hook output priority 0; policy drop;
# allow loopback
oif lo accept
# allow traffic to the proxy itself (DNS + HTTP/HTTPS)
ip daddr 172.20.0.2 tcp dport { 80, 443 } accept
ip daddr 172.20.0.2 udp dport 53 accept
# allow established/related (return traffic)
ct state established,related accept
# log and drop everything else
log prefix "iron-proxy-drop: " drop
}
}
docker-compose.yml: das Client-Image wird mit vorinstalliertem nftables
erstellt. Der Entrypoint lädt die Regeln und führt dann die Demo aus.
Zum Laden der Regeln ist CAP_NET_ADMIN erforderlich:```yaml
services:
proxy:
# ... same as DNS example ...
networks:
demo:
ipv4_address: 172.20.0.2
client: build: context: . dockerfile: Dockerfile.client # alpine + curl + nftables dns: - 172.20.0.2 cap_add: - NET_ADMIN volumes: - ./nftables.conf:/etc/nftables.conf:ro - certs:/certs:ro networks: demo: ipv4_address: 172.20.0.4
In einer Produktionsumgebung würden Sie die Regeln in einem Entrypoint-Wrapper laden und dann
`exec` Ihren eigentlichen Prozess als Nicht-Root-Benutzer ohne `CAP_NET_ADMIN` ausführen.
### TPROXY (transparenter Proxy)
Für Umgebungen, in denen Sie die DNS des Workloads überhaupt nicht kontrollieren können, kann nftables
TPROXY den Datenverkehr auf Kernel-Ebene umleiten, ohne dass der Workload
irgendwie mitwirken muss. Dies fängt Pakete in der PREROUTING-Kette ab und reicht sie
direkt an iron-proxy weiter:```
table ip iron {
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
# redirect HTTP/HTTPS to iron-proxy via TPROXY
tcp dport 80 tproxy to 172.20.0.2:80 meta mark set 1 accept
tcp dport 443 tproxy to 172.20.0.2:443 meta mark set 1 accept
}
chain output {
type route hook output priority mangle; policy accept;
# mark locally-originated packets for policy routing
tcp dport { 80, 443 } meta mark set 1
}
}
Dies erfordert die Einrichtung von ip rule und ip route, um markierte Pakete an einen lokalen Socket weiterzuleiten, und iron-proxy muss mit IP_TRANSPARENT binden. Dies ist komplexer einzurichten, bietet aber die stärkste Garantie dafür, dass Datenverkehr den Proxy nicht umgehen kann. TPROXY arbeitet unterhalb von DNS und fängt dadurch fest codierte IPs, benutzerdefinierte Resolver und alles andere ab, was die Workload versuchen könnte.
Das Verzeichnis examples/docker-compose enthält eine funktionierende Einrichtung. Die wichtigsten Bausteine:
docker-compose.yml: Proxy und Client in einem gemeinsamen Bridge-Netzwerk. Echte Geheimnisse werden nur auf dem Proxy-Container als Umgebungsvariablen gesetzt:```yaml services: proxy: build: context: ../.. dockerfile: examples/docker-compose/Dockerfile environment: - OPENAI_API_KEY=sk-real-openai-key-do-not-share - INTERNAL_TOKEN=real-internal-secret-value volumes: - certs:/certs networks: demo: ipv4_address: 172.20.0.2
client: image: alpine:latest dns: - 172.20.0.2 # Point DNS at the proxy volumes: - certs:/certs:ro networks: demo: ipv4_address: 172.20.0.4
**proxy.yaml** erlaubt `httpbin.org` und `icanhazip.com`, tauscht zwei Secrets aus:```yaml
transforms:
- name: allowlist
config:
domains:
- "httpbin.org"
- "icanhazip.com"
cidrs:
- "172.20.0.0/24"
- name: secrets
config:
secrets:
- source:
type: env
var: OPENAI_API_KEY
replace:
proxy_value: "proxy-openai-abc123"
match_headers: ["Authorization"]
match_query: true # scan the query string
rules:
- host: "httpbin.org"
- source:
type: env
var: INTERNAL_TOKEN
proxy_value: "proxy-internal-tok"
match_headers: [] # scan all headers
rules:
- host: "httpbin.org"
Das Client-Skript sendet fünf Anfragen, um jedes Verhalten zu demonstrieren:```bash
curl https://example.com/
curl -H "Authorization: Bearer proxy-openai-abc123" https://httpbin.org/headers
curl -H "X-Internal: proxy-internal-tok" https://httpbin.org/headers
curl "https://httpbin.org/get?token=proxy-openai-abc123&q=hello"
## Audit-Log-Format
Jede weitergeleitete Anfrage erzeugt einen strukturierten JSON-Protokolleintrag:```json
{
"host": "httpbin.org",
"method": "GET",
"path": "/headers",
"action": "allow",
"status_code": 200,
"duration_ms": 142,
"request_transforms": [
{
"name": "allowlist",
"action": "continue"
},
{
"name": "secrets",
"action": "continue",
"annotations": {
"swapped": [{ "secret": "OPENAI_API_KEY", "locations": ["header:Authorization"] }]
}
}
],
"response_transforms": []
}
Abgelehnte Anfragen enthalten ein rejected_by-Feld und werden auf WARN-Ebene protokolliert.
Audit-Ereignisse können als strukturierte OpenTelemetry-Protokolldatensätze für die Offline-Analyse in Backends wie Axiom, ClickHouse oder Logfire exportiert werden. Setzen Sie OTEL_EXPORTER_OTLP_ENDPOINT, um den Export zu aktivieren:```bash
docker run -d --name iron-proxy
-e OTEL_EXPORTER_OTLP_ENDPOINT=https://logfire-us.pydantic.dev
-e OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
-e OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer "
-e OTEL_SERVICE_NAME=iron-proxy
-e OTEL_RESOURCE_ATTRIBUTES="deployment.environment=staging" \
ironsh/iron-proxy:latest -config /etc/iron-proxy/proxy.yaml
All configuration uses standard OTEL environment variables:
| Variable | Beschreibung | Standard |
| ------------------------------ | ------------------------------------------------------- | ---------------- |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP-Collector-URL. Export deaktiviert ohne Wert. | (deaktiviert) |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `http/protobuf` oder `grpc`. | `http/protobuf` |
| `OTEL_EXPORTER_OTLP_HEADERS` | Kommagetrennte `key=value`-Paare für Auth-Header. | (keine) |
| `OTEL_SERVICE_NAME` | Dienstname für alle Protokolldatensätze. | `iron-proxy` |
| `OTEL_RESOURCE_ATTRIBUTES` | Kommagetrennte `key=value`-Ressourcenattribute. | (keine) |
Wenn aktiviert, wird jedes Audit-Ereignis als OTEL-Protokolldatensatz zusammen mit den vorhandenen JSON-Stderr-Protokollen ausgegeben. Der Protokolldatensatz verwendet dasselbe Schema wie der JSON-Audit-Eintrag: `host`, `method`, `path`, `action`, `status_code`, `duration_ms` sowie die vollständigen `request_transforms`/`response_transforms`-Arrays mit Anmerkungen.
## Verwaltungs-API
iron-proxy kann optional eine authentifizierte HTTP-API für betriebliche Aufgaben bereitstellen. Derzeit stellt sie einen einzigen Endpunkt bereit, `POST /v1/reload`, der die YAML-Konfiguration von der Festplatte neu einliest und atomar eine frisch erstellte Transform-Pipeline austauscht. Die laufende Pipeline bleibt erhalten, falls die neue Konfiguration ungültig ist.
Der Verwaltungsserver ist standardmäßig deaktiviert. Um ihn zu aktivieren, fügen Sie Ihrer Konfiguration einen `management`-Block hinzu:```yaml
management:
# Bind on loopback unless you front this with a private network or auth proxy:
# /v1/reload can rebuild the entire transform pipeline.
listen: "127.0.0.1:9092"
# Env var that holds the bearer token. Defaults to IRON_MANAGEMENT_API_KEY.
api_key_env: "IRON_MANAGEMENT_API_KEY"
Nur Standalone-Modus — nicht kompatibel mit dem vom Control-Plane verwalteten Modus.
Lade einen laufenden Proxy neu:```bash
curl -X POST http://127.0.0.1:9092/v1/reload
-H "Authorization: Bearer $IRON_MANAGEMENT_API_KEY"
## iron.sh
Benötigen Sie Vault/KMS-Secret-Backends, einen Kubernetes-Operator oder zentrales Policy-Management? [iron.sh](https://iron.sh) baut auf iron-proxy auf und bietet Enterprise-Funktionen für Teams, die dies in großem Umfang betreiben.
## Release-Signaturen verifizieren
Release-Artefakte enthalten ein signiertes Prüfsummen-Manifest:
- `checksums.txt`
- `checksums.txt.asc` (ASCII-armored abgetrennte Signatur)
Verwenden Sie den enthaltenen öffentlichen Schlüssel unter [`public-key.asc`](https://github.com/paradigmxyz/iron-proxy/blob/HEAD/public-key.asc) zur Verifizierung:```bash
# 1) Download release artifacts for a tag
TAG=vX.Y.Z
gh release download "$TAG" --pattern "checksums.txt" --pattern "checksums.txt.asc"
# 2) Import the project signing key
gpg --import public-key.asc
# 3) Verify the signature over checksums.txt
gpg --verify checksums.txt.asc checksums.txt
Wenn die Verifizierung erfolgreich ist, meldet GPG eine gültige Signatur von Matthew Slipper <[email protected]>.
Sie können optional den Fingerabdruck des importierten Schlüssels überprüfen und bestätigen, dass er mit Ihrer vertrauenswürdigen Quelle übereinstimmt, bevor Sie die Verifizierung durchführen.
Um eine bestimmte Binärdatei anhand der signierten Prüfsummenliste zu verifizieren (Beispiel: iron-proxy-linux-amd64):```bash
shasum -a 256 iron-proxy-linux-amd64 | grep -F "$(grep -F 'iron-proxy-linux-amd64' checksums.txt | awk '{print $1}')"
body_capture | Zeichnet decodierte Anforderungs-Bodys passender Hosts als request_body-Auditfelder auf. Nur Beobachtung; lehnt nie ab. |
require: wenn true, werden Anfragen an einen passenden Host, die kein
Proxy-Token enthalten, mit 403 abgelehnt. Dies verhindert, dass eine kompromittierte Workload
den Secret-Austauschmechanismus mit alternativen Anmeldedaten umgeht. Standard: false.hosts: schränkt den Austausch auf bestimmte Domains oder CIDRs ein.ttlfailure_ttlaws_ssm: liest name aus AWS Systems Manager Parameter Store. Optionale
region, with_decryption, ttl und failure_ttl werden unterstützt.
with_decryption ist standardmäßig true, was die erwartete Einstellung für
SecureString-Parameter ist.1password: löst secret_ref (eine op://vault/item/[section/]field
Referenz) mithilfe eines 1Password-Service-Account-Tokens auf. Das Token wird aus
OP_SERVICE_ACCOUNT_TOKEN gelesen. Optionale ttl und failure_ttl werden unterstützt.1password_connect: löst dieselbe op://vault/item/[section/]field
secret_ref gegen einen selbst gehosteten 1Password-Connect-Server auf. Die Server-URL
wird aus OP_CONNECT_HOST und das API-Token aus OP_CONNECT_TOKEN gelesen.
Optionale ttl und failure_ttl werden unterstützt.labels.0equalsinmatcheswhenmcp im Audit-Logeintrag aufgezeichnet: Servername, Richtung (request oder response), Methode, Tool, Entscheidung (allow, deny oder filtered), Grund bei Ablehnungen und die Anzahl der bei Filterereignissen entfernten Tools.