Where it happens
Running rodney inside the Claude Code for web sandbox — an agent/CI environment whose
entire HTTPS egress is forced through a local, policy-enforcing MITM proxy exposed as
HTTPS_PROXY=http://127.0.0.1:<port>, with a custom CA bundle on disk and an SNI allow-list.
$ rodney start
$ rodney open https://example.com/
panic: navigation failed: net::ERR_CONNECTION_RESET
curl and openssl s_client reach the same URL through the very same proxy without any
problem, so the environment's egress itself is fine — the failure is specific to how Chrome
is launched.
What I think the root cause is
I dug into this with Chrome's --log-net-log and by comparing Chrome's ClientHello against
openssl's. My suspicion is a stack of four issues, all rooted in the sandbox's MITM egress
proxy:
- Chrome is never pointed at the proxy.
detectProxy() only activates Chrome's proxy
when the proxy URL carries credentials (user:pass). This sandbox's proxy is
credential-less, so rodney launches Chrome with no --proxy-server; Chrome tries a
direct connection, the sandbox firewall drops it, and navigation resets. (curl/openssl
honor HTTPS_PROXY and therefore work.)
- The MITM CA isn't trusted. Hosts the proxy terminates present the sandbox's own CA,
which Chrome's bundled root store doesn't know → ERR_CERT_AUTHORITY_INVALID
(is_issued_by_known_root:false in the netlog).
- Secure DNS bypasses the proxy. Chrome's DNS-over-HTTPS tries public resolvers
(e.g. 8.8.8.8:443) directly, around the proxy; in the locked-down sandbox those are
dropped → ERR_NAME_NOT_RESOLVED.
- The proxy resets Chrome's TLS 1.3 ClientHello (main cause). Even after routing +
cert-trust + DNS are handled, HTTPS still resets right after the CONNECT … 200 tunnel
is established, inside the origin TLS handshake (SOCKET_READ_ERROR net_error=-101,
ECONNRESET, immediately after the ClientHello). The proxy appears to peek at the tunneled
ClientHello to enforce its SNI allow-list, and its parser chokes on Chrome's TLS 1.3
ClientHello. Decoding the two hellos:
- Chrome (~586 B): SNI, key_share, ALPN, GREASE, and crucially an
Encrypted-ClientHello (ECH) GREASE extension (0xfe0d).
- openssl (~308 B): plain — no ECH, no GREASE, no ALPN → passes cleanly.
Notably, this Chromium build (1321438) keeps emitting the ECH GREASE extension even with
--disable-features=EncryptedClientHello, so the offending extension can't be removed via
that flag. For example.com the proxy doesn't even MITM (openssl gets the real Cloudflare
origin cert), it just selectively resets Chrome's hello — which points at ClientHello
inspection rather than TLS termination.
Workaround I'm using
Launching Chrome (via rodney) with, in effect:
- route through the proxy (
--proxy-server=$HTTPS_PROXY, loopback on a bypass list),
--ignore-certificate-errors (for the MITM CA),
- disable DoH,
--ssl-version-max=tls1.2 — a TLS 1.2 ClientHello carries no ECH / no key_share, so the
proxy's inspector lets it through.
With that, rodney open https://example.com → rodney title returns Example Domain,
screenshots work, etc.
This is a pragmatic workaround for middleboxes that choke on TLS 1.3 / ECH ClientHellos; a
TLS-1.3-preserving fix would need the ECH GREASE extension gone (not possible via flag in this
build) or the proxy's parser fixed — both outside rodney's scope.
Suggestion
Would it make sense for rodney to also route Chrome through a credential-less
HTTPS_PROXY (not just authenticated proxies), and optionally expose an escape hatch
(env var / flag) for these sandbox constraints — cert-error tolerance, disabling DoH, and
capping TLS at 1.2 on the proxy path? Happy to open a PR if that direction sounds reasonable.
Where it happens
Running
rodneyinside the Claude Code for web sandbox — an agent/CI environment whoseentire HTTPS egress is forced through a local, policy-enforcing MITM proxy exposed as
HTTPS_PROXY=http://127.0.0.1:<port>, with a custom CA bundle on disk and an SNI allow-list.curlandopenssl s_clientreach the same URL through the very same proxy without anyproblem, so the environment's egress itself is fine — the failure is specific to how Chrome
is launched.
What I think the root cause is
I dug into this with Chrome's
--log-net-logand by comparing Chrome's ClientHello againstopenssl's. My suspicion is a stack of four issues, all rooted in the sandbox's MITM egressproxy:
detectProxy()only activates Chrome's proxywhen the proxy URL carries credentials (
user:pass). This sandbox's proxy iscredential-less, so
rodneylaunches Chrome with no--proxy-server; Chrome tries adirect connection, the sandbox firewall drops it, and navigation resets. (curl/openssl
honor
HTTPS_PROXYand therefore work.)which Chrome's bundled root store doesn't know →
ERR_CERT_AUTHORITY_INVALID(
is_issued_by_known_root:falsein the netlog).(e.g.
8.8.8.8:443) directly, around the proxy; in the locked-down sandbox those aredropped →
ERR_NAME_NOT_RESOLVED.cert-trust + DNS are handled, HTTPS still resets right after the
CONNECT … 200tunnelis established, inside the origin TLS handshake (
SOCKET_READ_ERROR net_error=-101,ECONNRESET, immediately after the ClientHello). The proxy appears to peek at the tunneled
ClientHello to enforce its SNI allow-list, and its parser chokes on Chrome's TLS 1.3
ClientHello. Decoding the two hellos:
Encrypted-ClientHello (ECH) GREASE extension (
0xfe0d).Notably, this Chromium build (1321438) keeps emitting the ECH GREASE extension even with
--disable-features=EncryptedClientHello, so the offending extension can't be removed viathat flag. For example.com the proxy doesn't even MITM (openssl gets the real Cloudflare
origin cert), it just selectively resets Chrome's hello — which points at ClientHello
inspection rather than TLS termination.
Workaround I'm using
Launching Chrome (via
rodney) with, in effect:--proxy-server=$HTTPS_PROXY, loopback on a bypass list),--ignore-certificate-errors(for the MITM CA),--ssl-version-max=tls1.2— a TLS 1.2 ClientHello carries no ECH / no key_share, so theproxy's inspector lets it through.
With that,
rodney open https://example.com→rodney titlereturnsExample Domain,screenshots work, etc.
This is a pragmatic workaround for middleboxes that choke on TLS 1.3 / ECH ClientHellos; a
TLS-1.3-preserving fix would need the ECH GREASE extension gone (not possible via flag in this
build) or the proxy's parser fixed — both outside
rodney's scope.Suggestion
Would it make sense for
rodneyto also route Chrome through a credential-lessHTTPS_PROXY(not just authenticated proxies), and optionally expose an escape hatch(env var / flag) for these sandbox constraints — cert-error tolerance, disabling DoH, and
capping TLS at 1.2 on the proxy path? Happy to open a PR if that direction sounds reasonable.