Learn Claude Code: Safety Sandboxing: OS-Level Enforcement

Sandboxing: OS-Level Enforcement

Advanced 🕐 15 min Lesson 5 of 12
What you'll learn
  • Explain what sandboxing adds over permission rules and why OS-level enforcement is necessary for full isolation
  • Configure sandbox filesystem and network boundaries, enable auto-allow mode, and enforce the sandbox in production environments
  • Recognize the domain fronting limitation of the built-in network proxy and understand when a custom TLS-terminating proxy is necessary

The Gap Permission Rules Cannot Fill

Permission rules control what Claude Code attempts to do. They evaluate the command string before the command runs and decide whether to allow, deny, or prompt. But once a command runs, permission rules have no further control over what it does. If a Bash command spawns a child process, that child process inherits the same filesystem access and network connectivity as the parent — and permission rules know nothing about it.

Consider a build script that Claude Code approves based on its name. The script runs, and at some point it calls a subprocess that reads a credential file and sends it to an external endpoint. No permission rule catches this because the subprocess is not a direct Claude Code tool call. Sandboxing catches it — by restricting what processes can access at the OS level, regardless of how they were invoked.

How the Sandbox Works

Claude Code's sandbox uses OS-level security primitives to enforce filesystem and network boundaries around every Bash command and its descendant processes:

  • macOS: Uses Seatbelt, the macOS kernel sandbox framework. Nothing to install.
  • Linux and WSL2: Uses bubblewrap for filesystem isolation and socat to route network traffic through the sandbox proxy. Install with sudo apt-get install bubblewrap socat (Ubuntu/Debian) before enabling the sandbox.
  • Windows: Not supported natively. Run Claude Code inside WSL2.

Enable the sandbox in an interactive session with /sandbox, which opens a three-tab panel: Mode (how sandboxed commands are approved), Overrides (the escape hatch behavior), and Config (the resolved settings). The Mode tab is where you choose between auto-allow and regular permissions.

Two Sandbox Modes

The sandbox has its own two-level approval model, separate from the permission mode:

  • Auto-allow mode: Sandboxed Bash commands run without prompting. The sandbox boundary substitutes for the permission prompt — instead of asking before each command, the OS enforces the boundary after. Commands that cannot run sandboxed (incompatible tools, non-allowed network hosts) fall back to the regular permission flow.
  • Regular permissions mode: All Bash commands go through the regular permission flow even when sandboxed. More control, more approvals.

Auto-allow mode still enforces deny rules, rm-of-root circuit breakers, and content-scoped ask rules like Bash(git push *). A bare Bash ask rule is skipped for commands that run sandboxed in auto-allow mode — the sandbox boundary replaces it.

Sandbox and Auto Mode Are Independent Layers

The sandbox's auto-allow mode and --permission-mode auto are two entirely separate things that can be combined:

Sandbox auto-allow mode

Controls whether sandboxed Bash commands need permission prompts. The sandbox boundary is what provides safety.

Auto permission mode

Controls whether all tool calls (Bash, file edits, WebFetch, MCP) pass through the classifier for behavioral review before running.

Using both together gives you OS-level isolation on Bash commands (sandbox) AND behavioral review of all tool calls including file edits and network requests (auto mode classifier). They address different threat models and compose cleanly.

Filesystem Boundaries

By default, sandboxed commands can write only to the working directory and the session temp directory ($TMPDIR). They can read from the entire filesystem by default — this is intentional so build tools and dependency installers work without configuration, but it means credential files like ~/.aws/credentials are readable unless you explicitly block them (covered in the next lesson).

Configure the boundaries with sandbox settings:

  • sandbox.filesystem.allowWrite: ["~/.kube", "/tmp/build"] — grant write access to additional paths
  • sandbox.filesystem.denyWrite: ["~/"] — block writes to home directory and below
  • sandbox.filesystem.denyRead: ["~/"] — block reads from home directory
  • sandbox.filesystem.allowRead: ["."] — re-allow reads inside a denied region (project root when in project settings)

When the same array key is defined across multiple settings scopes, the arrays are merged — not replaced. A developer can extend the lists but cannot remove entries that managed settings provide.

Network Isolation

The sandbox routes all network traffic through a proxy. No domains are pre-allowed — the first time a sandboxed command needs a new domain, Claude Code prompts for approval. Pre-allow domains with sandbox.network.allowedDomains in settings to avoid the prompt for known-safe hosts like your internal artifact registry.

The escape hatch (allowUnsandboxedCommands) lets Claude retry commands that fail under the sandbox outside the sandbox boundary. Set it to false in settings (or the Overrides tab calls it Strict sandbox mode) to prevent this — in strict mode, commands that the sandbox cannot run must either be added to excludedCommands or fail the task.

The Domain Fronting Limitation

The built-in proxy makes its allow decision from the client-supplied hostname without inspecting TLS traffic. This means the sandbox correctly blocks connections to evil.com if that domain is not in your allowlist — but it cannot prevent a technique called domain fronting, where a sandboxed process routes a request through an allowed domain (like github.com) to reach a different backend.

For most development workflows this is an acceptable limitation. For high-security environments — classified networks, regulated industries, or deployments where data exfiltration via a trusted domain is a credible threat — configure a custom TLS-terminating proxy that decrypts and inspects HTTPS traffic, then point Claude Code at it via the sandbox.network.httpProxyPort and sandbox.network.socksProxyPort settings. Install its CA certificate inside the sandbox so tools do not reject the certificate.

Similarly, allowing broad domains like *.github.com or *.amazonaws.com creates a large trusted surface. Be specific about which subdomains you actually need.

Enforcing the Sandbox Organizationally

Add to managed settings to require the sandbox for every developer:

{ "sandbox": { "enabled": true, "failIfUnavailable": true, "allowUnsandboxedCommands": false } }

failIfUnavailable: true blocks Claude Code from starting if bubblewrap or socat is missing rather than falling back to unsandboxed execution. allowUnsandboxedCommands: false disables the escape hatch so commands that fail under the sandbox cannot retry outside it. Together they turn the sandbox from a best-effort control into a hard gate.

Key takeaways
  • Sandboxing enforces filesystem and network boundaries at the OS level for every Bash command and its child processes — it catches what slips through permission rules after a command is approved.
  • The sandbox has its own auto-allow mode that is separate from --permission-mode auto — they address different things and can be combined for OS-level isolation plus behavioral review.
  • The built-in sandbox proxy does not inspect TLS traffic by default, creating a domain fronting risk — for high-security environments, configure a custom TLS-terminating proxy instead of relying on the hostname-only allowlist.
  • failIfUnavailable:true and allowUnsandboxedCommands:false in managed settings turn the sandbox from a best-effort control into a hard gate that blocks Claude Code startup if the sandbox cannot initialize.
  • Allowing /var/run/docker.sock via allowUnixSockets effectively grants host system access through the Docker socket — Unix socket allowances require the same scrutiny as broad filesystem write permissions.