# Claude Code security: when /sandbox is not enough

> OS-level Bash sandboxing cut Anthropic's permission prompts ~84%, but /sandbox only wraps Bash. Pick the right isolation tier and deny credential reads.

URL: https://bartlomiejkrupa.dev/articles/claude-code-security-sandboxing-2026/
Published: 2026-07-17
Updated: 2026-10-07
Tags: claude-code, security, sandboxing, permissions, prompt-injection
Summary: OS-level Bash sandboxing cut Anthropic's permission prompts ~84%, but /sandbox only wraps Bash. Pick the right isolation tier and deny credential reads.

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 [permission rules block](/notes/permission-rules-block) and the [tools inventory and `ToolName(specifier)` rules](/articles/claude-code-tools).

**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. The garden note [Sandbox ladder](/notes/sandbox-ladder) is the one-liner version of this section.

| 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](/articles/claude-code-plan-mode). 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`

```text
/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 `$TMPDIR` only
- **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). Set `allowUnsandboxedCommands: false` for 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.

```json
{
  "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 `credentials` protects nothing.
- `mask` (v2.1.221+, Linux and WSL) substitutes a sentinel inside the sandbox and injects the real secret only on listed `injectHosts`. It requires `network.tlsTerminate`. Mask entries in project settings are ignored; use user, managed, or CLI settings.
- Broad allowlists like `github.com` still 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.sock` through `allowUnixSockets`. 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-review` and PR review actions.** Useful first pass, not a gate: a model review is not deterministic, so two runs on the same diff can flag different things. Pair it 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

1. **Trusted personal repo, interactive:** `/sandbox` auto-allow + credential denies + narrow `allowedDomains`
2. **Team laptop fleet:** managed `enabled` + `failIfUnavailable` + `allowUnsandboxedCommands: false` + `sandbox.credentials` for AWS/SSH/tokens
3. **Unattended / skip-permissions:** container, VM, or `npx @anthropic-ai/sandbox-runtime claude` with write/network allowlists that include the project, `~/.claude`, and `api.anthropic.com`
4. **Untrusted repository:** dedicated VM or Claude Code on the web; do not rely on Bash sandbox alone
5. **Always:** review suggested commands; deny `curl`/`wget` if 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](/articles/claude-code-commands-cheatsheet-2026).

## 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.

Sources:
- Configure the sandboxed Bash tool: https://code.claude.com/docs/en/sandboxing
- Choose a sandbox environment: https://code.claude.com/docs/en/sandbox-environments
- Security: https://code.claude.com/docs/en/security
- Making Claude Code more secure and autonomous with sandboxing: https://www.anthropic.com/engineering/claude-code-sandboxing
- Claude Code changelog - 2.1.187, 2.1.191, 2.1.221: https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
