Repository navigation
Conversation
gakonst
marked this pull request as ready for review
October 28, 2026 21:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in reverse listener. It's for clients that can neither trust iron-proxy's MITM CA nor route through its DNS or
HTTPS_PROXY. The motivating case is a Cloudflare Worker, whosefetchonly trusts public CAs and has no proxy setting. Today such a client can only send plain HTTP, which iron-proxy forwards upstream as plain HTTP. The tunnel listener also refuses absolute-formhttps://requests.The client reaches iron-proxy under its own name and names the upstream in the first path segment. For example,
https://proxy.example.com/api.anthropic.com/v1/messagesgoes tohttps://api.anthropic.com/v1/messages.tls.RequireAndVerifyClientCertagainstclient_ca, and config validation refuses a reverse listener without one. Anyone who can send it a request gets the credentials the transforms inject, so this is the access control. A Worker presents its certificate through anmtls_certificatesbinding.handleHTTPis split into the listener-specific checks andproxyRequest. The reverse handler rewritesHost, path andRawPath, then callsproxyRequestwith schemehttps. The allowlist, secrets, header allowlist, MCP policy and audit all see the upstream host, path and query, as on the other listeners. The upstream deny CIDRs still apply at dial time.//host/...) and dot segments after the host.Cf-*andCdn-Loopare dropped. Cloudflare addsCF-Workerto every Worker subrequest, and chatgpt.com answers requests that carry it with 403.wss://upstreams. WebSocket dials now use the upstream transport's TLS config, throughwebsocketTLSConfig, so they verify upstreams as HTTP requests do, offering onlyhttp/1.1in ALPN.http.Transportaddsh2to itsNextProtosonce it has served a request. The first version of this PR cloned that config, so chatgpt.com negotiated h2 and the client got HTTP/2 frames in reply to its HTTP/1.1 upgrade. The second commit fixes that, andTestReverse_WebSocketToTLSUpstreamnow checks the negotiated protocol; it failed withh2before the fix.The README documents the listener, including the Worker binding, and so does
iron-proxy.example.yaml.Tests.
internal/proxy/reverse_test.gocovers:Cf-*andCdn-Loop;http/1.1.internal/confighas validation cases for a missing cert/key and a missingclient_ca.go test ./...(excludingintegration_test),go test -racefor the touched packages,go vet ./...and staticcheck all pass.End to end, 2026-10-28. This branch ran on a GCP VM under systemd with a Let's Encrypt certificate for its
sslip.ioname and a client CA of our own. The allowlist was limited to the ChatGPT and Claude model endpoints, and thesecretstransform injected:iron-token-broker(built from 064f6cb) keeps refreshed;claude setup-tokentoken.A Cloudflare Worker running nanocode-ts presented its client certificate through an
mtls_certificatesbinding. It held no credentials. It ran real agent turns that computed a value in a Code Mode cell:responses_websocket_v2) completed in 6 s.The audit log shows each request allowed, with the secrets transform applied, and internet scanners refused at the handshake with
client didn't provide a certificate. Before the second commit, the Worker's WebSocket got Cloudflare's 520, and Node'swsreportedParse Error: Expected HTTP/.