anthropics / anthropics/claude-code
[BUG] Cloud sandbox proxy resets Chromium's TLS 1.3 handshake; WebKit unaffected (corrects #11791)
- 主要言語
- Python
- スター
- 145k
- フォーク
- 23.1k
- PR マージ指標
- PR 指標を取得中
説明
### 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.
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- javascript, node.js, playwright
- 領域
- cloud, infrastructure, networking
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 活発
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 28/100