Skip to content

Sandboxing

Turn on Claude Code's built-in Bash sandbox, set which files and hosts shell commands can reach, protect credentials, and fix what it breaks.

The Bash sandbox is an operating-system boundary around the shell commands Claude runs on your machine. You decide which paths those commands can write, which they can read, and which network hosts they can contact. The limits cover Bash, PowerShell and Monitor commands plus every process they spawn, and because the OS enforces them while the command runs, Claude Code can safely skip the approval prompt for sandboxed commands.

It runs on macOS, Linux and WSL2. Native Windows runs commands unsandboxed, so on a Windows machine use a WSL2 distribution.

I run with the sandbox on in auto-allow mode for most client work. It removes the stream of "can I run npm test?" prompts while still stopping a stray script from writing to my home directory or phoning out to a random host.

Note: This page is about the per-command sandbox on your own machine. For containers, VMs and wrapping the whole Claude Code process, see Sandbox environments. For cloud session isolation, see Claude Code on the web. For prompts on non-shell tools, see Permission modes.

What a sandboxed command can reach

The sandbox is off by default. Turn it on with /sandbox in a session, or set sandbox.enabled to true in a settings file.

AccessDefaultSettings that change it
WriteWorking directory, a per-user temp directory, and any directories added with --add-dir, /add-dir or permissions.additionalDirectories. Protected paths stay read-onlysandbox.filesystem.allowWrite, sandbox.filesystem.denyWrite
ReadNearly everything, including ~/.ssh and ~/.aws/credentialssandbox.filesystem.denyRead, sandbox.credentials
NetworkNo direct route. Traffic goes via a local proxy that checks each host against your allowed domains, which start emptysandbox.network.allowedDomains, sandbox.network.deniedDomains
EnvironmentInherits Claude Code's environment, secrets includedsandbox.credentials, CLAUDE_CODE_SUBPROCESS_ENV_SCRUB

Under the hood it uses the open source @anthropic-ai/sandbox-runtime package.

What the sandbox does not cover

Only shell commands are wrapped. These run outside it:

  • Built-in tools such as Read, Edit, Write, WebFetch and WebSearch. They follow permission rules instead, so a denyRead entry does not stop the Read tool and allowedDomains does not restrict WebFetch.
  • Helper processes: command hooks, local MCP servers, plugin monitors, LSP servers, your status line command and apiKeyHelper all run with your full access.

Some shell commands also escape it depending on configuration:

To put all of the above behind one boundary, run Claude Code itself in a container, VM or the sandbox runtime. See Sandbox environments.

Getting started

On macOS there is nothing to install; the sandbox uses Seatbelt. On Linux and WSL2 you need bubblewrap and socat (setup below), but you can run /sandbox first because it tells you what is missing.

  1. Run /sandbox. The panel has three tabs: Mode (how sandboxed commands are approved), Overrides (whether failed commands may retry unsandboxed, which is allowUnsandboxedCommands) and Config (the resolved settings). On Linux a Dependencies tab appears when something is missing; if it is the only tab, a required package is absent.
  2. On the Mode tab pick auto-allow or regular permissions.
  3. Ask Claude to run something real, such as your test suite. The first time a command needs a new host you are prompted (in auto mode Claude instead lists hosts on the command for the classifier).

Choices made in the panel are saved to .claude/settings.local.json, and Claude Code adds that file to your global gitignore. To turn the sandbox on everywhere, put sandbox.enabled: true in ~/.claude/settings.json; for a whole organisation, use managed settings.

For a one-off session without touching any file, pass --settings. This starts a session where blocked commands cannot fall back to running outside the sandbox:

claude --settings '{"sandbox": {"enabled": true, "allowUnsandboxedCommands": false}}'

Warning: If the sandbox cannot start (missing dependency, unsupported platform), Claude Code runs commands unsandboxed by default. Set sandbox.failIfUnavailable to true to make it exit at startup instead.

Check it is actually working

Ask Claude (not the ! prompt, which usually bypasses the sandbox) to run these:

CommandExpected inside the sandbox
touch ~/probe-sbxOperation not permitted on macOS, Read-only file system on Linux and WSL2
curl --noproxy '*' https://example.orgCould not resolve host, since there is no route round the proxy

Decline any offer to retry unsandboxed. If touch worked and your home directory is not a writable path, delete the file and check /sandbox.

Linux and WSL2 setup

You need bubblewrap (filesystem isolation) and socat (relays traffic to the proxy):

# Debian or Ubuntu
sudo apt-get install bubblewrap socat

# Fedora
sudo dnf install bubblewrap socat

Ripgrep ships inside the native Claude Code binary. The seccomp filter, which blocks Unix domain sockets, is optional; install it with npm install -g @anthropic-ai/sandbox-runtime. Dependencies are checked at startup, so restart Claude Code after installing.

Ubuntu 24.04 and later: AppArmor may stop bubblewrap creating user namespaces. Run sysctl kernel.apparmor_restrict_unprivileged_userns. If it prints 1, add an AppArmor profile for /usr/bin/bwrap that is flags=(unconfined) and grants userns, save it at /etc/apparmor.d/bwrap, then sudo systemctl reload apparmor. If it prints 0 or the key does not exist, skip this. The profile only affects bwrap, not what runs inside it.

WSL2: check the version with wsl -l -v in PowerShell. The message Sandboxing requires WSL2 means you are on WSL1. Launching Windows binaries (cmd.exe, powershell.exe, anything under /mnt/c/) goes over a Unix socket, so it follows the sandbox's socket settings; set allowAllUnixSockets if you need those launches.

Sandbox modes

Both modes enforce the same boundary. They differ only in whether sandboxed commands still prompt.

Auto-allow

A command that runs inside the sandbox is approved with no prompt. Commands that run outside it (excluded commands and unsandboxed retries) go through the normal permission flow. Even in auto-allow:

  • Deny rules always apply.
  • rm or rmdir on a critical path still goes through the normal flow.
  • Content-scoped ask rules such as Bash(git push *) still prompt.
  • A bare Bash ask rule (or Bash(*)) is skipped for sandboxed commands, except in plan mode, where it prompts even for read-only commands.

Auto-allow works independently of your permission mode, so sandboxed commands that edit files run without prompts even in Manual mode. The exceptions are plan mode (auto-allow does not widen approvals there), auto mode commands carrying per-command domains, and server-side classifier review in auto mode.

Regular permissions

Every Bash command goes through the normal permission flow, sandboxed or not. More control, more clicks.

The unsandboxed retry

When a command fails inside the sandbox, Claude may retry it with the dangerouslyDisableSandbox parameter, which runs it outside. If the sandbox blocked a connection, the denied host is named in the result so Claude can see why. Who approves the retry depends on the mode:

  • bypassPermissions: runs with no prompt.
  • Manual and acceptEdits: a prompt titled "Bash command (unsandboxed)".
  • Auto: the classifier judges the command.
  • dontAsk: denied.
  • Plan: as described in plan mode.

A matching allow rule (say Bash(curl *)) approves the retry too. To always be asked, add an ask rule for Bash(dangerouslyDisableSandbox:true); it applies even in auto and bypass, and outranks allow rules. With permissions.blockReadsOutsideWorkingDirectories on, some retries always prompt.

Turn off the retry with strict sandbox mode

Set "allowUnsandboxedCommands": false and Claude Code ignores dangerouslyDisableSandbox; everything Claude runs is sandboxed unless it matches excludedCommands. Pair it with failIfUnavailable so nothing runs unsandboxed if the sandbox cannot start. In /sandbox this appears on the Overrides tab as Strict sandbox mode.

A false in user settings, --settings or managed settings wins over a project's true (since v2.1.285). Setting it to false in managed settings or via --settings makes the sandbox admin-required.

Commands you type at ! still run unsandboxed unless the session is a background session or a Linux session with CLAUDE_CODE_SUBPROCESS_ENV_SCRUB set.

Temporary directories

Claude Code points $TMPDIR at a per-user writable temp directory for sandboxed commands. Unsandboxed commands use your shell's $TMPDIR, or CLAUDE_CODE_TMPDIR, or the OS temp directory if that is unset. Because the two differ, pass files between sandboxed and unsandboxed commands via the working directory.

Configuring the boundary

All of this lives under the sandbox key; the settings reference lists every key.

Extra write locations

Tools like kubectl, terraform or gradle often need to write outside the project. Grant just those paths rather than excluding the tool:

{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "allowWrite": ["~/.gradle", "/var/tmp/reports"]
    }
  }
}

Paths use normal conventions: /var/tmp/reports is absolute and ~/.gradle is under home. That differs from Read and Edit permission rules, where // means absolute and / means project-relative. Arrays from different settings scopes are merged rather than replaced, except where a managed lock covers them. Edits to these lists apply to the running session from the next command.

If you exclude a settings source with --setting-sources (or settingSources in the SDK), its sandbox.filesystem entries, Edit rules and Read deny rules are ignored when building the sandbox (v2.1.246).

Read restrictions

denyRead blocks paths, allowRead re-opens part of a denied region, and denyWrite blocks writes. Where read rules overlap, the narrower path wins:

RulesOutcome
denyRead: ["~/"], allowRead: ["~/work"]Only ~/work is readable under home
allowRead: ["~/"], denyRead: ["~/.netrc"]Everything except ~/.netrc
allowRead: ["~/"], denyRead: ["~/**/.env"]Every .env under home stays hidden

A project config that hides your home directory but keeps the project readable belongs in .claude/settings.json, because . resolves to the project root there (in ~/.claude/settings.json it would resolve to ~/.claude):

{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyRead": ["~/"],
      "allowRead": ["."]
    }
  }
}

For a simpler "only the working directories are readable" policy, turn on permissions.blockReadsOutsideWorkingDirectories instead of writing rules.

Taking commands out with excludedCommands

sandbox.excludedCommands runs matching commands with no filesystem or network limits. Reserve it for tools that genuinely cannot work in the sandbox and that you trust with full access. If a tool only needs one more path or host, use allowWrite or allowedDomains.

{
  "sandbox": {
    "enabled": true,
    "excludedCommands": ["podman *", "watchman *"]
  }
}

Matching rules:

  • Patterns use Bash(...) rule syntax. No wildcard means an exact match, so end with * to allow arguments.
  • Every command in a chained call must match, so pnpm i && podman build . stays sandboxed unless pnpm i is excluded too.
  • Matching is on the literal text: a script that calls podman internally does not match, nor does /usr/bin/podman.
  • Redirects to a file, cd, and $(...) substitutions keep the whole call sandboxed.
  • Under an admin-required sandbox, entries in project settings files are ignored.

Excluded commands then go through the normal permission flow: read-only and allow-listed commands run, the classifier reviews the rest in auto mode, and bypass runs them unless an ask rule matches. To test a pattern, switch to Manual and run something matching; the prompt is titled "Bash command (unsandboxed)".

Warning: An excluded command has your full access. A pattern that covers an interpreter, a script in the working directory, or a tool that reads a file there (like a compose file) lets Claude write that file and then run it outside the sandbox. Keep patterns narrow.

Turning off filesystem isolation

Set sandbox.filesystem.disabled to true (v2.1.216 or later, default false) to keep network isolation but drop file restrictions. Useful when you care about where commands connect, not what they write:

{
  "sandbox": {
    "enabled": true,
    "filesystem": { "disabled": true },
    "network": { "allowedDomains": ["pypi.org", "*.pythonhosted.org"] }
  }
}

Warning: With filesystem isolation off and auto-allow on, a command can rewrite shell startup files, binaries on $PATH or ~/.claude/settings.json to widen its own access next time. Only do this for trusted workloads.

Who can set it: user settings, managed settings and --settings, never project files. If managed settings configure sandbox.filesystem at all, or include a credentials.files entry with "mode": "deny", only managed settings can set it. A valid mask entry does not lock it. CLAUDE_CODE_SUBPROCESS_ENV_SCRUB forces isolation back on from every source.

With it off: denyRead and file deny credential entries stop being enforced; environment variable deny and mask entries still apply; file mask entries applied as masks still apply. Sandboxed commands also inherit your shell's $TMPDIR, and autoAllowBashIfSandboxed still defaults to true.

Protecting credentials

sandbox.credentials lists files and environment variables to hide from sandboxed commands, each with a mode. With "mode": "deny", files become unreadable (part of the filesystem layer) and variables are unset before each command (independent of it).

{
  "sandbox": {
    "enabled": true,
    "credentials": {
      "files": [
        { "path": "~/.config/gcloud", "mode": "deny" },
        { "path": "~/.netrc", "mode": "deny" }
      ],
      "envVars": [
        { "name": "STRIPE_SECRET_KEY", "mode": "deny" },
        { "name": "OPENAI_API_KEY", "mode": "deny" }
      ]
    }
  }
}

Deny entries merge across scopes and can only narrow access. There is no built-in deny list; only what you list is hidden. Excluding project or local settings drops their credential entries (v2.1.246); excluding user settings keeps its deny entries and file masks (as restrictions only) but drops its env var masks. To scrub secrets from every subprocess, sandboxed or not, use CLAUDE_CODE_SUBPROCESS_ENV_SCRUB.

Masking instead of hiding

"mode": "mask" shows the command a per-session placeholder (the sentinel), and the proxy swaps in the real value on outbound requests to permitted hosts. The command can still authenticate without ever holding the secret. Requirements:

  • TLS termination: set network.tlsTerminate so the proxy can see request bodies. Without it the sentinel goes out unchanged and authentication fails (nothing leaks). claude doctor warns with TLS termination is unavailable.
  • Allowed destinations: injectHosts lists where the real value may go, and each must also be in allowedDomains. With no injectHosts, every allowed domain gets it.
  • Trusted scope: mask entries, tlsTerminate, credentials.allowPlaintextInject, awsPairs and sigv4 are honoured only from user, managed or --settings, never project files. Delivered via server-managed settings, they need approval.
{
  "sandbox": {
    "enabled": true,
    "network": {
      "tlsTerminate": {},
      "allowedDomains": ["api.stripe.com", "registry.npmjs.org"]
    },
    "credentials": {
      "envVars": [
        { "name": "STRIPE_SECRET_KEY", "mode": "mask", "injectHosts": ["api.stripe.com"] },
        { "name": "NPM_TOKEN", "mode": "mask" }
      ]
    }
  }
}

A deny for the same variable in any scope wins over mask. For structured values such as connection strings or JWTs, the extract, decode, maskClaims and onExtractNoMatch fields let tools keep parsing the value. For IPv6, write "[::1]" in allowedDomains but bare "::1" in injectHosts; claude doctor flags injectHosts entries that can never match (v2.1.229).

AWS: mask AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY together so the proxy can re-sign SigV4 requests (v2.1.221). The conventional names, plus AWS_SESSION_TOKEN, are linked automatically; group differently named variables with credentials.awsPairs (v2.1.224). Streaming uploads, presigned URLs and SigV4A cannot be re-signed and fail at the proxy; credentials.sigv4 forwards them so AWS returns its own rejection.

Files (v2.1.221): on Linux and WSL2 commands read a sentinel copy and the proxy substitutes on the way out. On macOS the file is simply unreadable, like deny. Use extract to mask just the secret inside a larger file. If the pattern matches nothing, the default onExtractNoMatch: "warn" skips the entry and leaves the real file readable, so use deny where that matters. Directories, globs, files over 8 MiB and non-UTF-8 files fall back to deny.

How the enforcement works

Filesystem

Writes are allowed in the working directory and its subfolders, added directories and the per-user temp directory. Reads are allowed almost everywhere. With permissions.blockReadsOutsideWorkingDirectories on, home and other user-file directories become unreadable too. In a linked git worktree, writes to the main repo's shared .git are allowed so commits work, except its hooks/ and config.

Protected paths

Even inside writable directories, the sandbox denies writes to files Claude Code loads configuration or code from, so a command cannot grant itself permissions or plant a hook:

  • Working directory and its parents: .claude settings files, .claude/skills, .claude/agents, .claude/commands, .claude/hooks, .mcp.json, and files Claude Code runs itself such as .claude/workflows and .claude/scheduled_tasks.json.
  • Working directory only: shell startup files like .bashrc and .zshrc, .gitconfig, .vscode, .idea, and .git/hooks and .git/config.
  • Bare-repo markers at the top level: HEAD, objects, refs, plus config and hooks beside a HEAD (a config file is denied regardless). On Linux and WSL2, any of the first three that appear during a command are deleted.
  • ~/.claude (or CLAUDE_CONFIG_DIR): most contents, plus ~/.claude.json and .credentials.json.

A symlink that appears at one of these paths also has its target protected from the next command on. No allowWrite or Edit rule lifts this; only filesystem.disabled does. The Config tab in /sandbox lists most of them under Denied within allowed. This list is separate from the permission system's protected paths, which govern approval before a tool runs.

Network

On Linux and WSL2 commands run in a network namespace with no connection out; on macOS Seatbelt blocks everything except the local proxy. Claude Code sets HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and friends so tools find the proxy.

  • Tools that honour proxy variables (curl, npm, git over HTTPS) work once the host is allowed. A host entry without a port allows every port.
  • Tools that ignore them (plain ssh, most database drivers) cannot connect at all.
  • Non-TCP traffic (UDP, QUIC, ICMP ping) never leaves.

How hosts get allowed:

  • Prompts: "Yes" allows the host for the session; "Yes, and don't ask again" saves a WebFetch(domain:...) rule to local settings (user settings when the sandbox is admin-required).
  • Pre-allowing: allowedDomains, plus any WebFetch(domain:...) allow rules. Those rules support a leading *. and a bare * (v2.1.186); other wildcard positions have no effect on the sandbox.
  • strictAllowlist (v2.1.219): from user, managed or --settings, refuse unlisted hosts instead of prompting. Ignored in project files.
  • allowManagedDomainsOnly: in managed settings, only managed allowedDomains and WebFetch rules count, and everything else is refused.
  • Corporate proxy: set HTTPS_PROXY, HTTP_PROXY and NO_PROXY (ideally in the settings env block so background agents get them). The allowlist is applied first, then allowed traffic is tunnelled upstream.

The proxy checks hostnames but does not inspect TLS by default.

Unlisted hosts by permission mode: bypass (and plan with bypass available) allows them; Manual, acceptEdits and plain plan prompt; auto refuses unless the command listed the host and the classifier approved; dontAsk refuses. strictAllowlist, allowManagedDomainsOnly and deniedDomains refuse in every mode.

Local addresses: once a hostname passes the allowlist, the proxy refuses it if it resolves only to loopback, link-local (including 169.254.169.254) or your own machine's addresses. localhost and *.localhost may resolve to loopback. Private ranges such as 10.0.0.0/8 are fine. To allow a refused address, add the IP itself, for example "127.0.0.1:5173".

Per-command domains in auto mode (v2.1.271): each sandboxed command can carry its own list of hosts (domains, *. wildcards or IPs, optional :port). The classifier reviews them with the command, and an approved list opens those hosts for that command only. Such commands always go to the classifier rather than being approved by rules or auto-allow. deniedDomains still block, locked allowlists refuse per-command lists, and connections to unlisted hosts are refused outright with the host named so Claude can retry.

IPv6: write "[::1]" for all ports or "[::1]:443" for one (v2.1.229). Unbracketed ambiguous entries are denied under every reading in deny lists and narrowed or dropped in allow lists. claude doctor reports them.

OS primitives

macOS uses Seatbelt; Linux and WSL2 use bubblewrap. You can also run the runtime package standalone around Claude Code; see Sandbox environments.

Sandbox, rules and modes together

Permission rules are checked before any tool runs and apply to every tool. The sandbox is enforced by the OS on the running process, so it holds even if an approved command does more than its name suggests. Both feed the final sandbox configuration:

SourceEffect on the sandbox
sandbox.filesystem.allowWrite / Edit allow rulesExtra writable paths
sandbox.filesystem.denyWrite, denyRead / Read and Edit deny rulesBlocked paths
sandbox.filesystem.allowReadRe-opens part of a denied read region
sandbox.filesystem.disabledDrops the filesystem layer
allowedDomains / WebFetch(domain:...) allow rulesReachable hosts
deniedDomains / WebFetch(domain:...) deny rulesBlocked hosts, even under a broader wildcard

/sandbox is not a permission mode. Auto-allow approves Bash commands because the boundary contains them; auto mode approves actions because a classifier reviewed them; --dangerously-skip-permissions approves everything and also skips protected-path checks. They combine, with the exceptions noted under Sandbox modes. See Permission modes for common pairings.

Enforcing it across an organisation

Deliver sandbox keys through managed settings (an MDM-managed file or server-managed settings). A solid baseline:

{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "credentials": {
      "files": [
        { "path": "~/.aws", "mode": "deny" },
        { "path": "~/.ssh", "mode": "deny" }
      ]
    }
  }
}

Because this makes the sandbox admin-required, also put any approved excludedCommands, allowWrite paths and socket entries in managed settings, since repositories can no longer supply them. Developers can still run commands themselves at !. Native Windows cannot run the sandbox, so with failIfUnavailable those machines will not start; deliver the policy only to macOS and Linux, or move Windows users to WSL2 or a container.

Stopping developers widening it

Managed booleans override local values, but arrays merge. These keys weaken the sandbox and can be turned on by user settings, --settings or (unless admin-required) project files, so set them to false in managed settings if you do not want them: enableWeakerNestedSandbox, enableWeakerNetworkIsolation, network.allowAllUnixSockets, network.allowLocalBinding, and allowAppleEvents (which projects can never enable). Also consider allowManagedReadPathsOnly: true and allowManagedDomainsOnly: true.

Repository settings under an admin-required sandbox

The sandbox is admin-required when managed settings set allowUnsandboxedCommands: false (or --settings does, unless managed says true) or allowManagedDomainsOnly: true. Neither turns the sandbox on, so set enabled too. Then, from a repository's .claude/settings.json and .claude/settings.local.json, Claude Code ignores (v2.1.285):

  • excludedCommands, ignoreViolations, network.allowedDomains, network.allowUnixSockets, network.allowMachLookup, network.httpProxyPort, network.socksProxyPort
  • The sandbox write access from filesystem.allowWrite, Edit allow rules and additionalDirectories (file tools still honour them)
  • The sandbox host from WebFetch(domain:...) allow rules (WebFetch still honours them)
  • true for the four weakening keys above
  • false for enabled or failIfUnavailable when the user file says true
  • allowRead entries under paths denied by managed, --settings or user settings

Repository deny entries and autoAllowBashIfSandboxed still apply.

Some locks work without admin-required mode (v2.1.285): deniedDomains or a WebFetch deny rule in managed or --settings disables a repo's proxy ports; strictAllowlist in managed, --settings or user settings disables a repo's proxy ports, allowedDomains and WebFetch allow rules; denyRead, a Read deny rule or a credentials.files entry in managed or --settings disables repo grants that would reach those paths.

Using your own proxy

To inspect or log sandbox traffic, point sandboxed commands at a proxy you run locally:

{
  "sandbox": {
    "network": {
      "httpProxyPort": 3128,
      "socksProxyPort": 1080
    }
  }
}

Once a port is set, your proxy does all the filtering: allowedDomains, deniedDomains, strictAllowlist, prompts and the local-address check stop applying to that traffic, and HTTPS_PROXY is no longer chained. Ports can be set only in managed settings when allowManagedDomainsOnly is on; in managed, --settings or user settings when admin-required or a network lock applies; otherwise anywhere.

Troubleshooting

Under an admin-required sandbox, put fixes in ~/.claude/settings.json rather than project files.

  • Host not allowed: approve it or add it to allowedDomains; with allowManagedDomainsOnly, ask an admin.
  • jest hangs: watchman does not work in the sandbox; run jest --no-watchman.
  • Go CLIs (gh, gcloud, terraform) fail TLS on macOS: exclude them, for example gh *, or with a MITM proxy and custom CA set enableWeakerNetworkIsolation.
  • open, osascript or browser auth fails with -600 on macOS: Apple Events are blocked. Set allowAppleEvents in user, managed or CLI settings (this removes code-execution isolation) or exclude open *.
  • docker fails: it is incompatible; exclude the specific subcommands you need.
  • Clipboard tools (pbcopy, xclip, wl-copy) do nothing: ask Claude to print the text and run /copy. Excluding the tool does not help when Claude pipes into it.
  • git fails with unable to unlink old: it needs to replace a protected or non-writable file. Approve the unsandboxed retry or run git yourself.
  • bubblewrap: Can't mount proc on /newroot/proc in an unprivileged container: set enableWeakerNestedSandbox, only if the container is already your boundary.
  • 0-byte read-only files at .claude settings paths and "don't ask again" will not save (Linux, WSL2): placeholders left by a killed session. claude doctor lists them under Stale sandbox mask files left by a killed session; delete them with no other session running.
  • git over SSH: fails on macOS even for allowed hosts. Switch the remote to HTTPS or exclude git fetch *, git pull *, git push * (calls with cd, -C or substitutions stay sandboxed). On Linux it works if port 22 is allowed, your upstream proxy permits it, and your key file is readable.
  • Database clients and plain ssh: they ignore proxy variables and show Operation not permitted or Network is unreachable. Exclude the specific command, ideally with a matching ask rule.
  • Cannot reach a localhost server: on macOS set network.allowLocalBinding (which exposes every local port). On Linux the sandbox's localhost is private, so exclude the command instead.
  • resolved to a loopback address for a dev hostname: add both the name and the IP with the port, such as "shop.test:4000" and "127.0.0.1:4000".
  • /sandbox says settings are overridden by a higher-priority configuration: --settings or managed settings set enabled, autoAllowBashIfSandboxed or allowUnsandboxedCommands. /status shows the sources.

Limitations

  • TLS is not inspected by default, so allowing a broad domain such as github.com opens exfiltration routes, and domain fronting may reach unlisted hosts. Use a custom inspecting proxy if that matters. tlsTerminate enables masking only, not content filtering.
  • Unix sockets can be escalation paths; allowing /var/run/docker.sock is effectively host access.
  • Broad write grants to $PATH directories, system config or shell rc files enable escalation.
  • enableWeakerNestedSandbox significantly weakens Linux isolation.
  • allowAppleEvents lets commands launch unsandboxed apps on macOS.
  • Scope: computer use acts on your real desktop; subagents share the parent's sandbox config; background sessions follow their own settings; processes started by mods run outside the sandbox.

Warning: Filesystem and network isolation only work as a pair. Without the network layer a compromised command can send your SSH keys out; without the filesystem layer it can backdoor something that later gets network access. Check each widening against the other side.