Built-in Bash sandbox: network isolation unreliable on WSL2; failIfUnavailable doesn't detect a non-enforcing proxy

Status Open
Reported on v2.1.215
Maintainer reply None cached
Activity 0 comments · opened Jul 20, 2026

Summary

On WSL2, the built-in Bash sandbox's network isolation is unreliable: the out-of-sandbox proxy (which sandboxed processes reach at localhost:3128) frequently never starts, and when it does start it sometimes does not enforce network.allowedDomains. Filesystem isolation works correctly on the same host. Critically, sandbox.failIfUnavailable: true does not catch this — it verifies the sandbox binaries exist, not that the network proxy actually started and enforces — so the sandbox silently fails open or fails closed with no error.

Environment

  • Claude Code 2.1.215
  • WSL2, kernel 6.6.87.2-microsoft-standard-WSL2
  • bubblewrap 0.9.0, socat present, @anthropic-ai/sandbox-runtime 0.0.66 installed
  • sysctl kernel.apparmor_restrict_unprivileged_userns → key does not exist (no AppArmor userns restriction)
  • No ambient proxy: ~/.curlrc, /etc/curlrc, git http.proxy, and *_PROXY env vars all absent (the localhost:3128 seen inside the sandbox is Claude Code's own injected proxy, not an external one)

Settings (user scope, $HOME/.claude/settings.json)

{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": { "allowedDomains": ["myorg.crm.dynamics.com"] }
  }
}

Repro

Run headless in a trusted workspace (fresh HOME needs $HOME/.claude.json with projects["<cwd>"].hasTrustDialogAccepted: true, else it runs degraded — see note 3):

claude -p --dangerously-skip-permissions --allowedTools Bash --model sonnet \
  'run: curl -sS -o /dev/null -w "ORG=%{http_code}\n" https://myorg.crm.dynamics.com ;
   then: curl -sS -o /dev/null -w "EX=%{http_code}\n" https://example.com && echo WEB_OK || echo WEB_BLOCKED'

Observed (across ~10 runs, non-nested, plain terminal)

Three outcomes, non-deterministically:

  1. Proxy dead (most common): both curls fail with curl: (7) Failed to connect to localhost port 3128. The allowed org host is unreachable too — not just a policy block.
  2. Proxy up but not enforcing: ORG=302 and example.com also succeeds (WEB_OK) — the denied host egresses.
  3. Correct (rare): ORG=302, example.com blocked.

Expected: deterministically outcome 3 — allowed host reachable, all others blocked.

Notes

  1. failIfUnavailable is binaries-only. It never aborted in any of the above, including when the proxy was dead and every request failed. It does not detect a non-functional/non-enforcing network proxy at runtime, so a broken network sandbox silently fails open (outcome 2, exfiltration risk) or fails closed for all traffic (outcome 1).
  2. Filesystem isolation works on the same host/run (writes outside cwd are denied with Read-only file system), so bubblewrap itself is functioning — the failure is specific to the network proxy / socat relay into the sandbox netns.
  3. Untrusted workspace degrades silently. With an untrusted fresh HOME, settings.json's permissions.allow entries are ignored and the sandbox runs in a degraded mode that injects HTTP_PROXY=localhost:3128 but never starts the proxy → every request dies at the dead proxy. A warning is printed about permissions, but nothing indicates the sandbox itself is non-functional. Trusting the workspace is what enabled outcome 3 to occur at all.

Impact

Any automated/headless use of the built-in sandbox for network isolation on WSL2 cannot rely on it: failIfUnavailable gives a false sense of a fail-closed guarantee, while the actual enforcement is flaky. We had to add a runtime preflight probe (drive one sandboxed request, assert an allowed host is reachable and a denied host is blocked) to fail closed, and document "run on native Linux, not WSL2."

Ask

  • Make failIfUnavailable (or a companion setting) verify the network proxy actually started/enforces, not just that binaries exist.
  • Fix the WSL2 proxy/socat-relay startup reliability, or document WSL2 as unsupported for sandbox network isolation.
  • Consider making untrusted-workspace either enforce the sandbox or fail loudly, rather than injecting a dead proxy.

View original on GitHub ↗