Back oddguan.com Second Time Same Sandbox Anthropic Claude Code Network Allowlist Bypass Data Exfiltration
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:
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.
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.
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.
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.
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:
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.
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:
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.
The bypass is most dangerous when paired with prompt injection, the attack class I covered in and Control . A hidden instruction in a GitHub issue , 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:
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.
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 a security fix.
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 the CVE plan, the answer was:
“We have not yet decided whether a CVE will be published for this issue and can’t a timeline on that decision.” — Anthropic (VDP), 2026-04-07
Since the patch shipped, Anthropic has issued:
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.
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.
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.
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.
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.
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 variants involve whatever encoding the OS layer might disagree with the JS layer : Unicode normalization, IDN homographs, percent-encoded escapes, trailing dots, IPv6 zone identifiers.
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 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.
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.
Aonan Guan | Lead Cloud & AI Security at Wyze Labs | Security Researcher · GitHub · oddguan.com
Prior research on the “ and Control” prompt-injection attack chain has been covered by The Verge , The Register , The 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 .
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
