GUIDE · INCIDENT 8 min ·

Claude Code's SOCKS5 sandbox bypassOne null byte. 130 versions. Five and a half months. Still no CVE.

Claude Code's network sandbox shipped in October 2025 to keep an agent from reaching arbitrary hosts. A single null byte in a SOCKS5 destination hostname broke the allowlist for the next 130 releases. The fix landed in a routine April 1, 2026 release with no security mention and no CVE.

TL;DR· the answer, in twenty seconds

What happened: Claude Code's network sandbox used a JavaScript endsWith() check on the SOCKS5 destination hostname. C getaddrinfo() stops at the first null byte. A hostname like attacker.com\x00.google.com passed the allowlist and resolved to the attacker. The bug shipped in v2.0.24 (Oct 2025) and got patched in v2.1.90 (Apr 2026) with no security note. 130 releases were vulnerable.

The minimum fix: Upgrade Claude Code to v2.1.90 or later today. Audit SOCKS5 traffic from any host that ran an older build. Rotate AWS, GitHub, and IMDS-reachable credentials if logs show outbound SOCKS5 you cannot explain.

The lesson: Allowlist checks in JavaScript do not know what a C resolver will do with the same string. A network sandbox is only as honest as its weakest serializer. And silent patches without a CVE leave defenders unable to triage at speed.

This one is not subtle. From October 20, 2025 to April 1, 2026, every released Claude Code build with the network sandbox enabled accepted a SOCKS5 destination hostname containing a null byte and routed the request through the resolver under a different name. About 130 versions were vulnerable. The fix landed on April Fool's day in v2.1.90 with no security mention in the release notes. As of mid-May, the SOCKS5 bypass still has no CVE in the NVD or the GitHub Advisory Database.

The researcher, Aonan Guan, filed HackerOne #3646509. Anthropic closed it as a duplicate. The only CVE on file in this neighborhood is CVE-2025-66479, which is filed against a separate sandbox-runtime component, not Claude Code itself.

Public disclosure landed on May 10. This article walks through the bug, why the sandbox was designed the way it was, what an attacker could pull off in practice, and what you do on Monday if you ran a vulnerable build on a machine that had production credentials.

The sandbox the bug lived inside

Claude Code's network sandbox shipped in v2.0.24 in late October 2025. The job was simple to describe: keep the agent from reaching hosts you did not explicitly allow. The implementation routed outbound traffic through an in-process SOCKS5 proxy that compared each destination against an allowlist of domain suffixes. Hosts not on the list got refused.

The decision to do the suffix match in TypeScript made sense at the time. The proxy was written in JavaScript, the allowlist was JavaScript data, and String.prototype.endsWith() is a one-liner. The problem is that the resolver downstream of the proxy is not JavaScript.

When SOCKS5 says "connect to this hostname," the server-side library has to turn that name into an IP. Most Node binaries hand the name to libc's getaddrinfo(). That function is C, and C strings end at the first null byte. JavaScript strings do not.

So the proxy and the resolver disagree on what a hostname is. The proxy thinks attacker.com\x00.google.com ends with .google.com. getaddrinfo() looks at the same buffer and sees attacker.com\0, stops at the null, and resolves the attacker's host. The allowlist passed; the kernel made a call the allowlist meant to block.

The pre-fix code passed the raw DOMAINNAME bytes from the SOCKS5 request straight to the check, with no null-byte rejection, no length cap, and no character whitelist. That single decision, repeated across 130 published builds, is the bug.

Why this beats a normal egress filter

If you only ran HTTPS through the sandbox, you would have noticed. The SNI on a TLS handshake to attacker.com does not match the SNI to google.com. You would see ALPN mismatches, certificate-name failures in the log, 200s to hosts you never typed.

SOCKS5 sidesteps all of that. The agent dials attacker.com:443 through the proxy, opens a raw TCP stream, and the proxy logs say it dialed google.com:443. The attacker can speak any protocol they want over that stream. There is no TLS layer to argue with the proxy about whose name it is. The HTTP egress audit log the sandbox was supposed to be the source of truth for shows the allowed name, every time.

This is the kind of bug that survives in production because the people watching the logs see exactly what they expect.

What the attacker could take

The exploitable surface on a Claude Code host is broader than people think. Anything the agent process can read becomes reachable once you have a network sink:

  • AWS credentials. ~/.aws/credentials and ~/.aws/config are world-unreadable in theory and process-readable in practice. The agent runs as you. It can cat them. Exfiltrating them through SOCKS5 is a few hundred bytes.
  • GitHub tokens. ~/.config/gh/hosts.yml for the gh CLI, ~/.ssh/id_* if you let the agent see them. A small base64 payload over the bypassed SOCKS5 channel.
  • Cloud instance metadata. The agent can hit the IMDS endpoint at 169.254.169.254 (the well-known link-local address) and pull short-lived role credentials from the IAM credentials path. The metadata service does not care that the request came from a tunneled SOCKS5 connection; it cares that the source is local. On EC2, GCE, and Azure instances running Claude Code as a development host, this is a one-shot path to a fresh IAM role.
  • Corporate intranet. Anything reachable on the local network is reachable through the SOCKS5 hop. Internal Jira, internal ~/.netrc-authenticated registries, anything that trusts source IP.
  • Whatever else lives in the process's environment. Provider API keys passed via env, ~/.netrc, ~/.npmrc auth tokens, dotfiles that nobody encrypted.

A single prompt-injection payload that causes the agent to run a bash heredoc with a curl through socks5h://localhost:<proxy>/ is enough. The agent does not need to be "convinced" the action is safe; the sandbox is supposed to refuse the destination. The bug is that it does not.

Check Point's earlier write-up on CVE-2025-59536, the command-injection bug in .claude/CLAUDE.md, established that crafted repository files can trigger Claude Code to run code. Combine that earlier primitive with the SOCKS5 bypass, and a malicious open-source repo can exfiltrate every credential on the developer's machine the first time they clone and open it. Both bugs are now patched, but anything that ran a build between October 2025 and April 2026 sat on a 5.5-month window where the chain was live.

What "silent patch" did to defenders

Anthropic shipped the fix on April 1, 2026 in v2.1.90 and did not say a word about it in the release notes. There is no CVE. The HackerOne report sits closed as a duplicate. The Claude Code security advisories page lists nothing under sandbox bypass as of this writing.

A defender who wanted to know "did my build ship with this bug, and for how long" had no answer until the public write-up landed on May 10. The Register and a small handful of security blogs picked it up; the mainstream coverage was thin. If you upgrade Claude Code automatically, you got the fix in early April and would never have known the gap existed. If you pin builds, you may still be running a vulnerable version today.

This is the second silent patch in the same neighborhood. The first bypass, CVE-2025-66479, was patched into v2.0.55 in late November 2025, filed against sandbox-runtime rather than Claude Code itself, with no advisory on the product page. The version-to-bug mapping that a normal IT team would build from the NVD does not catch either of these for Claude Code installations.

OWASP's LLM Top 10 has covered LLM06 (sensitive information disclosure) and LLM02 (insecure output handling) for two years. The Claude Code bug is a clean instance of both. The pattern of vendors shipping silent patches and skipping advisories is the part the framework does not yet enforce.

What to do on Monday

This is the punch list if you have or had Claude Code on a credential-bearing machine.

# 1. Confirm the build.
claude --version
# Anything below 2.1.90 is vulnerable. v2.1.90 shipped April 1, 2026.

# 2. Upgrade.
# Whichever channel you installed from:
brew upgrade claude-code           # Homebrew
npm install -g @anthropic-ai/claude-code@latest   # npm
# Or the install script from the docs page.

# 3. Pin the new floor in your version control / Dockerfile / Brewfile.
echo "claude-code >= 2.1.90" >> Brewfile  # or your equivalent

# 4. Audit egress logs from any host that ran an older build.
# Look for outbound SOCKS5 (TCP) you did not authorize.
sudo grep -E 'socks5|1080' /var/log/* 2>/dev/null | head
# Or pull from your network-layer egress logs / firewall.

# 5. Rotate anything the process could have read.
aws sts get-caller-identity     # confirm role
gh auth status                  # confirm token
# Then rotate: aws-vault rotate, gh auth refresh, etc.

The credential-rotation step is the one most people will want to skip. The honest answer: if the host ran a vulnerable build, you have no way of knowing whether the bypass was used against you. The logs were the source of truth, and the logs lied. Treat it as a credential-exposure event scoped to whatever the agent could read.

If you are on a managed engineering platform that automates Claude Code installs, this is the week to add a build-floor gate to your fleet management. Allow v2.1.90 and up. Block anything older. Same pattern for any future silent patches the vendor decides not to advertise.

The pattern the next sandbox should not repeat

The high-order lesson is older than this bug: an allowlist is a contract between the part of the code that does the check and the part that takes the action. If those two parts handle the same string differently, the contract is broken. JavaScript endsWith() and C getaddrinfo() are a textbook example. So are JS URL parsing versus Python's urllib, Go's net/url versus the operating system's resolver, and any check that walks Unicode while the action walks raw bytes.

A network sandbox that lives inside the agent's process is a hard problem for this reason. The agent's job is to talk to many systems, in many serializations, across many libraries. Every place where the same string crosses a serialization boundary is a potential bypass. The Claude Code SOCKS5 case is one. There are others waiting to be found.

The architectural alternative is to move the egress check out of the agent's process. A separate proxy that resolves the hostname itself, sees the resolved IP, and enforces the allowlist on the IP it is about to dial does not have a JS/C disagreement to exploit. That is more work than endsWith(), which is why everyone reaches for the cheap version first.

What this means for your stack

The minimum action this week is to confirm the Claude Code build on every developer laptop and CI runner. If anything is older than v2.1.90, upgrade. Pin the new floor in whatever version control you use. Audit your egress logs for outbound SOCKS5 traffic you cannot account for, and rotate the credentials any vulnerable host had access to. Treat it as a small breach until you can prove it was not one.

The architectural pattern that closes the rest of the gap is to stop letting any agent process read the credentials it works with. If the agent gets a reference to a secret and the broker writes the value into the child process at exec, then a sandbox bypass like this one exfiltrates a reference, not a credential. The blast radius of "the agent could read everything it could reach" shrinks to "the agent could see what it was already using for the brokered run."

hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, then bind the project where your agent runs. Once the agent receives references instead of values, an exfiltration bug at the network layer cannot leak what the process never had. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

A network sandbox is a worthwhile defense; this incident does not change that. What it changes is the expectation. The sandbox is one layer. Process isolation is another. Brokered credentials are a third. Pick more than one, because every layer has a null-byte day coming for it.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index