The more permission dialogs you click through, the less you read them. Claude Code’s OS-level Bash sandbox (filesystem isolation plus network isolation) cut those prompts by about 84% in Anthropic’s internal usage, and it keeps a prompt-injected agent inside boundaries the kernel enforces. Mid-2026 added sandbox.credentials, managed lockdown keys, and a documented isolation ladder. One catch still trips people: /sandbox wraps Bash only. Read, Edit, MCP, and hooks stay on the host until you climb that ladder.
Definitions
Permission rules - allow / ask / deny checks that decide whether a tool may run at all (Bash, Read, Edit, WebFetch, MCP, and others). Evaluated before the tool runs. See the tools inventory and ToolName(specifier) rules.
Bash sandbox (/sandbox) - OS-level filesystem and network restrictions on Bash commands and every child process they spawn. Seatbelt on macOS; bubblewrap on Linux and WSL2. Native Windows is unsupported; use WSL2 or a container.
Sandbox runtime - @anthropic-ai/sandbox-runtime, which wraps the entire Claude Code process (file tools, MCP, hooks) in the same Seatbelt/bubblewrap primitives.
MCP - Model Context Protocol; the standard for third-party tool servers the agent can call. Each server is code you run alongside Claude Code.
Approval fatigue - rubber-stamping dialogs until they stop being a control and become noise. Sandboxing replaces per-command “may I?” with boundaries you declare once.
Why prompts are not enough
Claude Code starts read-only. Safe commands such as ls, cat, and git status run without a prompt; most mutating Bash still needs approval. Fine as a default. Broken as your only control after fifty approvals in an hour.
Prompt injection makes the gap concrete. Malicious text in a README, a fetched page, or a dependency can steer the agent toward curl, credential reads, or shell-config writes. Claude Code mitigates some of that: it sanitizes input, never auto-approves network commands, keeps WebFetch results in an isolated context, and asks about (rather than runs) any command its rules do not match. No model is injection-proof. The useful question is: if the agent is compromised, what can it still touch?
Anthropic’s answer is two boundaries that must both hold:
| Boundary | What it stops |
|---|---|
| Filesystem isolation | Writing outside the allowed paths (e.g. ~/.bashrc, system binaries); with credentials / denyRead, also reading secrets |
| Network isolation | Connecting to hosts outside the domain allowlist (exfiltration, malware download) |
Skip network isolation and an agent that can still read ~/.ssh can ship the keys out. Skip filesystem isolation and it can backdoor a tool that later gets network access. You need both.
The isolation ladder (2026)
Match the threat to the boundary. Climb when you skip prompts or touch untrusted code.
| Approach | What is isolated | When to use it |
|---|---|---|
Sandboxed Bash (/sandbox) | Bash + children only | Everyday local work; fewer prompts inside a known repo |
@anthropic-ai/sandbox-runtime | Whole Claude process (tools, MCP, hooks) | Unattended runs without Docker; MCP you do not fully trust |
| Dev container / custom container | Full development environment | Team standard; --dangerously-skip-permissions with firewall allowlist |
| Virtual machine | Full OS / kernel separation | Untrusted repos; compliance needs strong separation |
| Claude Code on the web | Anthropic-managed VM; Git via scoped proxy | No local setup; clone from GitHub in an isolated cloud VM |
--dangerously-skip-permissions and long auto-mode sessions remove the human gate. Anthropic’s guidance: put those sessions in a container, a VM, or the sandbox runtime. Bash sandbox alone is not that boundary. Plan mode’s “read-only” promise is behavioral, not sandboxed. OS isolation is the hard layer.
Scope gap that bites
| Mechanism | Covers Bash children | Covers Read / Edit | Covers MCP / hooks |
|---|---|---|---|
| Permission rules | Pattern-matched only | Yes | Yes (per tool) |
/sandbox | Yes (OS-enforced) | No | No |
| Sandbox runtime / container / VM | Yes | Yes (inside boundary) | Yes |
Claude Code recognizes common file commands inside Bash (cat, sed, grep) and applies your Read/Edit deny rules to them too. A Python one-liner that opens ~/.ssh/id_rsa is not recognized, so it walks past those rules. OS sandbox plus sandbox.credentials closes that hole for Bash; permission denies close it for the Read tool. Set both.
Enable and configure /sandbox
/sandbox
Pick auto-allow (sandboxed Bash runs without prompts; out-of-bound access falls back to the normal permission flow) or regular permissions (isolation stays, every command still asks). On Linux/WSL2 install bubblewrap and socat first. macOS uses Seatbelt with nothing extra. WSL1 and native Windows are unsupported.
Defaults that matter:
- Write: working directory + session
$TMPDIRonly - Read: nearly the whole machine, including credential files, unless you deny them
- Network: no domains pre-allowed; the first time a command needs a new host, Claude Code prompts (from v2.1.191, approving keeps that host for the rest of the session)
- Escape hatch: on sandbox failure Claude may retry with
dangerouslyDisableSandbox(still goes through permissions). SetallowUnsandboxedCommands: falsefor strict mode
Project-local mode lands in .claude/settings.local.json (not committed). User-wide: sandbox.enabled: true in ~/.claude/settings.json. Orgs use managed settings.
Close the credential hole (v2.1.187+)
Default read policy was the quiet failure mode through early 2026: writes locked, reads wide open. sandbox.credentials groups file denies and env scrubbing. Deny entries merge across scopes - project, user, and managed; a project or user scope cannot loosen a deny that managed settings set.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/"],
"allowRead": ["."]
},
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "deny" },
{ "name": "NPM_TOKEN", "mode": "deny" }
]
},
"network": {
"allowedDomains": ["registry.npmjs.org", "api.github.com"]
}
},
"permissions": {
"deny": ["Read(~/.ssh/**)", "Read(~/.aws/**)", "Read(./.env*)"]
}
}
Notes:
- There is no built-in credential deny list. Empty
credentialsprotects nothing. mask(v2.1.199+) substitutes a sentinel inside the sandbox and injects the real secret only on listedinjectHosts. It requiresnetwork.tlsTerminate. Mask entries in project settings are ignored; use user, managed, or CLI settings.- Broad allowlists like
github.comstill leave room for domain fronting - smuggling traffic to a blocked host behind an allowed hostname. The default proxy only checks the hostname the client claims; it does not look inside the encrypted traffic. If you need content inspection, terminate TLS at a custom proxy. - Never allow
/var/run/docker.sockthroughallowUnixSockets. That is host access via Docker.
To scrub Anthropic and cloud-provider credentials from the environment of every subprocess (sandboxed or not), set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB.
Org lockdown
Managed settings can require the sandbox and refuse soft fallback:
| Key | Effect |
|---|---|
sandbox.enabled: true | Sandbox on for every developer |
failIfUnavailable: true | Missing bubblewrap / unsupported platform → hard fail, not warn-and-continue |
allowUnsandboxedCommands: false | Ignores dangerouslyDisableSandbox retries |
allowManagedDomainsOnly | Only managed domain allowlists apply |
allowManagedReadPathsOnly | Developers cannot widen allowRead locally |
excludedCommands still merges from every scope, so a developer can always append tools that run outside the sandbox. Keep the managed list narrow (docker * is a common necessary exception).
Cloud sessions add another tier. Each Claude Code on the web run is an Anthropic-managed VM with network controls, a Git proxy that keeps real tokens outside the sandbox, branch-restricted push, audit logs, and automatic cleanup. Remote Control - the phone/web UI that drives Claude Code on your own machine - is different: it talks to a local process, with no cloud VM and no Anthropic-managed sandbox.
What still sits outside the sandbox
Treat these as product limits, not bugs:
- MCP servers. Put them on allowlists. Anthropic does not security-audit third-party servers. Prefer servers you wrote or trust; gate them with permission rules.
/security-reviewand PR review actions. Useful first pass. Outside evaluations (including Semgrep’s) find inconsistency and blind spots on multi-file and business-logic issues. Pair with deterministic static and dynamic analysis (SAST/DAST) tools.- Windows WebDAV. Avoid enabling WebDAV or
\\*paths; they can trigger remote requests that bypass the permission system. - Home-directory trust. Claude Code asks you to trust a folder before working in it. Starting in
~extends that trust to everything under your home directory for the session - that trust resets the next time you launch Claude Code. Start from a project subdirectory instead. - Data plane. Isolation does not change what is sent to the model API. Sandbox controls local blast radius, not training or privacy policy.
Practical playbook
- Trusted personal repo, interactive:
/sandboxauto-allow + credential denies + narrowallowedDomains - Team laptop fleet: managed
enabled+failIfUnavailable+allowUnsandboxedCommands: false+sandbox.credentialsfor AWS/SSH/tokens - Unattended / skip-permissions: container, VM, or
npx @anthropic-ai/sandbox-runtime claudewith write/network allowlists that include the project,~/.claude, andapi.anthropic.com - Untrusted repository: dedicated VM or Claude Code on the web; do not rely on Bash sandbox alone
- Always: review suggested commands; deny
curl/wgetif your threat model requires it; audit with/permissions; keep SAST in CI
Slash commands for the control surface: /sandbox and /permissions. Details in the commands cheatsheet.
Bottom line
Permission rules decide whether a tool runs. The Bash sandbox decides what a running shell can touch. Containers and VMs decide whether the whole agent (including Read and MCP) shares your host. Turn on /sandbox, deny credential reads, and climb the ladder the moment you remove the human from the approval loop.