AnswerQA

How do I sandbox Claude Code so a compromised tool call cannot leak my SSH keys?

Answer

Enable Claude Code's built-in sandbox, which uses macOS Seatbelt or Linux bubblewrap to restrict filesystem writes to your project directory and block all network traffic except approved domains. Configure allowWrite, denyRead, and allowedDomains to define the exact boundary.

By Kalle Lamminpää Verified May 12, 2026

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:

PrefixResolves to
/tmp/buildAbsolute, 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 block Read(~/.ssh/id_rsa). Use permissions.deny rules 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 * to excludedCommands or run Claude Code outside the sandbox entirely.
  • You use watchman for test-watching. Use jest --no-watchman instead, or exclude watchman from the sandbox.
  • You run Claude Code inside a container that cannot create user namespaces. Use enableWeakerNestedSandbox: true only 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.

Sources

Was this helpful?