Sandboxing and permissions are separate controls. Permissions decide which tools Claude can call. Sandboxing restricts what those tools can actually reach at the OS level. Without sandboxing, a prompt-injected Claude that gets a Bash approval can read ~/.ssh/id_rsa and POST it to any server. With sandboxing, the attempt is blocked before the subprocess even opens a file descriptor.
Step 1: Install prerequisites (Linux and WSL2 only)
macOS has Seatbelt built in. On Ubuntu/Debian:
sudo apt-get install bubblewrap socat
On Ubuntu 24.04+, the default AppArmor policy blocks bubblewrap from creating user namespaces. Add an exception:
sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
abi <abi/4.0>,
include <tunables/global>
profile bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
include if exists <local/bwrap>
}
EOF
sudo systemctl reload apparmor
WSL1 is not supported at all because bubblewrap requires kernel namespaces only in WSL2.
Step 2: Enable via /sandbox and choose a mode
Run /sandbox inside a Claude Code session to get the mode picker. Two modes:
- Auto-allow: sandboxed bash commands run without a permission prompt. Commands that fall back outside the sandbox (network access to a new host, incompatible tools) still hit the regular permission flow.
- Regular permissions: all bash commands require explicit permission even when sandboxed. More prompts, more control.
Auto-allow is the point of sandboxing for most teams. Without it you still have approval fatigue.
Step 3: Configure the filesystem boundary
By default the sandbox grants read everywhere, write only inside the current working directory. Grant additional write paths in .claude/settings.json:
{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["~/.kube", "/tmp/build"],
"denyRead": ["~/"],
"allowRead": ["."]
}
}
}
With this config, Claude can read the project root but nothing else in ~. Writes to ~/.kube and /tmp/build are allowed. The path prefix determines how each entry resolves:
| Prefix | Resolves to |
|---|---|
/tmp/build | Absolute, from filesystem root |
~/.kube | $HOME/.kube |
./output in .claude/settings.json | <project-root>/output |
./output in ~/.claude/settings.json | ~/.claude/output |
Note: this prefix convention differs from Read/Edit permission rules, where //path means absolute and /path means project-relative. Sandbox paths use standard conventions.
Step 4: Configure the network boundary
By default all domains are blocked once sandboxing is active. Domains are added to the allowlist interactively as Claude requests them. To pre-approve a set:
{
"sandbox": {
"network": {
"allowedDomains": ["registry.npmjs.org", "api.github.com"],
"deniedDomains": ["metadata.google.internal"]
}
}
}
deniedDomains removes specific hosts from a wildcard match. Useful when you allow *.googleapis.com but need to block the metadata service.
Footguns
Path arrays merge across settings scopes, not replace. If managed settings allow writes to /opt/company-tools and your user settings add ~/.kube, both paths apply in the final config. You cannot use project settings to remove a path that managed settings opened. Audit sandbox.filesystem.allowWrite at every tier when tightening a rule.
Broad domain allowlists enable domain fronting. The sandbox proxy blocks by hostname without doing TLS inspection. Code running inside the sandbox can use domain fronting to reach hosts outside your allowlist, routing traffic through an allowed CDN domain. Allowing github.com or *.s3.amazonaws.com is wider than it looks. If your threat model requires stronger guarantees, configure a custom HTTPS proxy that terminates TLS (sandbox.network.httpProxyPort) and install its CA inside the sandbox.
The dangerouslyDisableSandbox escape hatch is real. When a sandboxed command fails due to a network or filesystem restriction, Claude is prompted to analyze the failure and may retry with dangerouslyDisableSandbox: true, which then hits the normal permission flow. Anthropic calls this an “intentional escape hatch.” For locked-down environments, disable it:
{
"sandbox": {
"allowUnsandboxedCommands": false
}
}
With this set, the escape hatch parameter is ignored entirely. Commands must either work inside the sandbox or be listed in excludedCommands.
enableWeakerNestedSandbox trades isolation for Docker compatibility. If you run Claude Code inside a Docker container that lacks privilege for bubblewrap namespaces, you can set this to true. The sandbox still runs but with significantly weakened isolation. Only enable this when external container-level isolation is already enforced by your platform.
sandbox.failIfUnavailable defaults to false. If bubblewrap cannot start (missing package, unsupported platform), Claude Code logs a warning and runs without sandboxing. For deployments that require sandboxing as a security gate, set it to true:
{
"sandbox": {
"failIfUnavailable": true
}
}
allowUnixSockets to /var/run/docker.sock is a host escape. Allowing a Unix socket to the Docker daemon gives the sandboxed process full access to the host through the Docker API. Think twice before adding any Unix socket path to the allowlist.
What sandboxing does not cover
The sandbox isolates bash subprocesses only. These tools run outside it:
- Read, Edit, Write: these use the permissions system directly, not the sandbox. A
denyRead: ["~/.ssh"]sandbox rule does not blockRead(~/.ssh/id_rsa). Usepermissions.denyrules to block the file tools. - Computer use: when Claude controls your desktop, it runs on your actual desktop, not in an isolated environment. Per-app permission prompts gate each application.
- MCP servers: MCP servers run as separate processes. They are not sandboxed unless you run them explicitly through
npx @anthropic-ai/sandbox-runtime <mcp-server-command>.
When NOT to use sandboxing
- Your commands need to control Docker or Podman. Docker is incompatible with the sandbox. Add
docker *toexcludedCommandsor run Claude Code outside the sandbox entirely. - You use
watchmanfor test-watching. Usejest --no-watchmaninstead, or excludewatchmanfrom the sandbox. - You run Claude Code inside a container that cannot create user namespaces. Use
enableWeakerNestedSandbox: trueonly if the container itself provides equivalent isolation, not as a general workaround. - Your team is still in early exploration mode. The approval flow when sandboxing adds friction to one-off tasks. Enable sandboxing when you move to a more autonomous workflow.