anthropics / anthropics/claude-code

[BUG] Cloud sandbox proxy resets Chromium's TLS 1.3 handshake; WebKit unaffected (corrects #11791)

Offen
#90,521 0 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen
area:claude-code-web area:networking area:sandbox bug has repro platform:web
Vorherrschende Sprache
Python
Sterne
145k
Forks
23.1k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

### Preflight Checklist

- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code

### What's Wrong?

In the Claude Code cloud sandbox, headless Chromium cannot complete a TLS 1.3 handshake through the egress proxy. Every external HTTPS host fails with `net::ERR_CONNECTION_RESET` — including hosts that are plainly allowlisted, and that `curl` and Node reach fine over the same proxy at the same moment.

WebKit is unaffected. Same proxy, same hosts, both navigation and in-page `fetch`, real 200s. Only Chromium fails, and only at TLS 1.3: capping it at TLS 1.2 makes the handshake succeed.

This corrects #11791, which is open and unanswered since Nov 2025. That issue concludes the proxy "does NOT support the HTTP CONNECT method" and that browser automation is fundamentally incompatible with the sandbox, recommending users switch to `requests` + BeautifulSoup. WebKit tunnelling successfully through that same proxy disproves it. The `ERR_TUNNEL_CONNECTION_FAILED` reported there is a 403 policy denial — a different failure from the reset described here, which is why the two look alike and aren't.

The practical cost: browser-based verification looks impossible in the sandbox when in fact WebKit works today and Chromium works with two flags.

### What Should Happen?

Chromium should tunnel through the egress proxy the way WebKit, curl and Node all do — completing a TLS 1.3 handshake and returning the page.

Instead the handshake is reset mid-exchange, and a connection is only possible by capping the browser at TLS 1.2.

### Error Messages/Logs

```shell
net::ERR_CONNECTION_RESET https://registry.npmjs.org/ (chromium, default)
net::ERR_CONNECTION_RESET https://api.anthropic.com/ (chromium, default)

Proxy-side log for the same attempt:
{ "kind": "ws_closed_mid_exchange",
"detail": "tunnel closed (code 1006, Connection ended) after 6s; 1806 B sent, 39 B received, client reading" }

~1800 bytes out (a Chromium ClientHello), 39 bytes back, then the tunnel dies.

For contrast, a genuinely denied host fails DIFFERENTLY — this is a 403 to
CONNECT, not a reset:
net::ERR_TUNNEL_CONNECTION_FAILED https://cdnjs.cloudflare.com/
```

### Steps to Reproduce

1. Open a Claude Code cloud (web) session with Playwright available.

2. Save this as `tls-probe.js`:

```js
const pw = require('playwright')
const PROXY = { server: process.env.HTTPS_PROXY, bypass: 'localhost,127.0.0.1' }

;(async () => {
for (const [name, launch] of [
['headless shell (default)', () => pw.chromium.launch({ proxy: PROXY })],
['headless shell + tls1.2', () => pw.chromium.launch({ proxy: PROXY, args: ['--ssl-version-max=tls1.2'] })],
['full chrome', () => pw.chromium.launch({ channel: 'chromium', proxy: PROXY })],
['full chrome + tls1.2', () => pw.chromium.launch({ channel: 'chromium', proxy: PROXY, args: ['--ssl-version-max=tls1.2'] })],
['webkit', () => pw.webkit.launch({ proxy: PROXY })],
]) {
const b = await launch()
const p = await (await b.newContext()).newPage()
try { console.log(name, 'OK', (await p.goto('https://registry.npmjs.org/')).status()) }
catch (e) { console.log(name, String(e).split('\n')[0]) }
await b.close()
}
})()
```

3. Run `node tls-probe.js`.

4. Compare against `curl https://registry.npmjs.org/` through the same proxy — it returns 200.

Observed:

| binary | flag | result |
|---|---|---|
| `chromium_headless_shell` | (none) | ERR_CONNECTION_RESET |
| `chromium_headless_shell` | `--ssl-version-max=tls1.2` | ERR_CONNECTION_RESET |
| full `chrome` | (none) | ERR_CONNECTION_RESET |
| full `chrome` | `--ssl-version-max=tls1.2` | **OK 200** |
| `webkit` | (none) | **OK 200** |

### Claude Model

Opus

### Is this a regression?

I don't know

### Last Working Version

_No response_

### Claude Code Version

2.1.251 (Claude Code)

### Platform

Anthropic API

### Operating System

Ubuntu/Debian Linux

### Terminal/Shell

Other

### Additional Information

WHAT ISOLATES IT

Capping at TLS 1.2 fixes it, so the failure is in the TLS 1.3 handshake specifically. Ruled out explicitly, so nobody re-treads them:

- Not CA trust — identical reset with `ignoreHTTPSErrors: true` AND with `--ignore-certificate-errors`. It dies below certificate validation.
- Not the allowlist — the same hosts return real HTTP to curl through the same proxy, and denied hosts fail differently (ERR_TUNNEL_CONNECTION_FAILED = 403 to CONNECT).
- Not post-quantum key agreement — disabling PostQuantumKyber, X25519MLKEM768 and TLS13PostQuantumKeyAgreement changed nothing.
- Not ECH — disabling EncryptedClientHello changed nothing.

Node's and curl's TLS 1.3 handshakes to the same hosts succeed, so it appears specific to how Chromium composes its ClientHello — plausibly fragmentation across TLS records, or an extension the gateway mishandles. This is a known class of middlebox bug (Chrome disabled TLS 1.3 in Chrome 56 over it), but this instance doesn't appear to be reported.

WHY THE KNOWN TLS-1.2 WORKAROUND APPEARS NOT TO WORK

`chromium_headless_shell` silently ignores `--ssl-version-max`. Playwright 1.49+ resolves a bare `chromium.launch()` to the headless shell, so anyone applying the documented mitigation gets an unchanged reset and concludes the mitigation is broken. It needs `channel: 'chromium'`. Rows 2 and 4 in the table above are the same flag against different binaries.

ENVIRONMENT NOTE

This is Claude Code on the web — a managed cloud container, not a local
terminal, which is why Terminal/Shell is "Other". Ubuntu 24.04. The CLI version
is whatever the platform provisions rather than something I control. The finding
is in the sandbox egress proxy, not the CLI, and is independent of CLI version.

Playwright 1.62.1, PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers.

ASKS

1. Fix the gateway's TLS 1.3 negotiation with Chromium.
2. Until then, document the WebKit path and the `channel: 'chromium'` + `--ssl-version-max=tls1.2` fallback.
3. Correct or supersede #11791.

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Rechercherichtung

Start by running the provided tls-probe.js with Playwright and compare Chromium, WebKit, and curl through the same HTTPS_PROXY. Use the proxy log and the reported TLS 1.3 versus TLS 1.2 results to investigate the gateway behavior. Done means Chromium completes the TLS 1.3 handshake, or the WebKit path and channel: 'chromium' TLS 1.2 fallback are documented and #11791 is corrected or superseded.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, node.js, playwright
Bereich
cloud, infrastructure, networking
Issue-Typ
Bug
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Aktiv
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
28/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.