[BUG] Sandbox network allowlist bypassed via in-sandbox relayed CONNECT (direct CONNECT correctly blocked)
Preflight Checklist
- [x] I have searched existing issues 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?
Security impact
The sandbox domain allowlist (allowManagedDomainsOnly: true) can be bypassed by any sandboxed process that forwards its own CONNECTs through a trivial in-sandbox local relay. A direct proxied CONNECT to a non-allowlisted host is correctly blocked (403), but the same host is reachable when the CONNECT is relayed — in the same session, with the same credentials, targeting the same authority. In practice this means allowManagedDomainsOnly does not actually constrain the set of reachable destinations.
Summary
The sandbox network allowlist (sandbox.network.allowedDomains with allowManagedDomainsOnly: true) is enforced inconsistently depending on how a CONNECT reaches the sandbox proxy. A direct proxied request to a non-allowlisted host is correctly blocked (HTTP 403), but a request to the same host is allowed through when forwarded by a local relay process running inside the same sandboxed command. Both occur in the same session, minutes apart, so the block on non-allowlisted domains is not effective in practice.
Environment
- Sandbox:
enabled: true,allowUnsandboxedCommands: false,failIfUnavailable: true,allowManagedDomainsOnly: true. allowedDomainslists a few infra hosts (e.g.api.example-registry.com); the tested host (blocked.example.com) is not listed.- Linux container; bubblewrap with
--unshare-netand an in-namespacesocatbridge onlocalhost:3128→ host proxy.
Steps to reproduce
- Direct probe via the proxy:
``sh``
curl -sS -o /dev/null -w '%{http_code}\n' https://blocked.example.com/
# → 403 (correctly blocked)
- In the same session, run a sandboxed command that starts a small local HTTP CONNECT relay on
127.0.0.1:<port>(forwarding tolocalhost:3128, injecting the sandbox's own proxy credentials), then launch a child process configured to use127.0.0.1:<port>as its proxy and connect tohttps://blocked.example.com/.
- Re-run the step-1 probe:
``sh``
curl -sS -o /dev/null -w '%{http_code}\n' https://blocked.example.com/
# → still 403
Expected behavior
Step 2 is also blocked — blocked.example.com is not allowlisted, so no path should reach it.
Actual behavior
Step 2 succeeds; the child reaches blocked.example.com. Steps 1 and 3 stay 403. The allowlist is enforced for the direct CONNECT but bypassed for the relayed CONNECT, in the same session.
Ruled out
- Not session/resume/time dependent: reproduces in a brand-new session; the 403 and the success occur in the same session minutes apart.
- Not unsandboxed fallback: during the relayed run, PID 1 is
bwrap … --unshare-netin an isolated net namespace;allowUnsandboxedCommands: false. - Not a config grant: host absent from
settings.json; nosettings.local.json; no matching rule in~/.claude.json, user, or managed settings; no/etc/claude-code;/permissionsshows only configured tool rules; PreToolUse hooks can only deny. - Command-scoped, not agent-scoped: two Bash calls in one subagent — direct
curl(403) and the relay-launching command (allowed) — differ only in the command string.
Diagnostic detail
Both CONNECTs carry the sandbox's per-call <user>:<pass> proxy credentials and target the same authority; the only difference is direct issuance vs. forwarding by an in-sandbox relay. Enforcement diverges between the two.
How this differs from prior sandbox bypasses
This is a distinct mechanism from the two previously reported Claude Code network-sandbox bypasses. It is not a validation-layer trick (no crafted hostname) and not a config-parsing fault (the allowlist parses and enforces correctly for the direct path); enforcement simply diverges by how the CONNECT reaches the proxy.
- Not CVE-2025-66479 (empty-allowlist fail-open): that bug was a config-parsing fault where a misinterpreted/empty allowlist was treated as "allow everything." Here the allowlist is populated and is actively enforced — the direct probe to
blocked.example.comreturns 403 in the same session. - Not the SOCKS5 hostname null-byte injection (fixed ~v2.1.88–2.1.90): that bug was a parser differential —
endsWith()approved a crafted hostname likeattacker.com\x00.google.comwhile libc'sgetaddrinfo()truncated at the null byte. Here there is no malformed hostname: both CONNECTs target the same well-formed, non-allowlisted authority, and the allowlist check itself is not being fooled. - Not the
allowedDomainsover-blocking reports (e.g. legitimate domains being incorrectly blocked): this is the opposite failure — a non-allowlisted domain that should be blocked is reachable.
The divergence is purely direct-issuance vs. in-sandbox relayed-forwarding of an otherwise identical, correctly-credentialed CONNECT to a non-allowlisted host.
What Should Happen?
Access or blocking of a site in the Claude sandbox should not be affected by other (non address) details of the connection
Error Messages/Logs
Steps to Reproduce
- Direct probe via the proxy:
``sh``
curl -sS -o /dev/null -w '%{http_code}\n' https://blocked.example.com/
# → 403 (correctly blocked)
- In the same session, run a sandboxed command that starts a small local HTTP CONNECT relay on
127.0.0.1:<port>(forwarding tolocalhost:3128, injecting the sandbox's own proxy credentials), then launch a child process configured to use127.0.0.1:<port>as its proxy and connect tohttps://blocked.example.com/.
- Re-run the step-1 probe:
``sh``
curl -sS -o /dev/null -w '%{http_code}\n' https://blocked.example.com/
# → still 403
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.233
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Windows Terminal
Additional Information
_No response_