Articles › AI

Sandbox an AI coding agent: file and network limits via bubblewrap and Seatbelt

Let an AI coding agent run commands without approving each one, via an OS-level sandbox: file and network limits, defaults to know, and what it does not cover.

Intermediate 5 min read October 23, 2026

What you will learn
  • An OS-level sandbox (bubblewrap on Linux, Seatbelt on macOS) lets the agent run commands on its own inside a boundary; Anthropic reports 84% fewer permission prompts in internal use
  • You need both layers, filesystem and network; either one alone still leaks
  • Claude Code's default lets sandboxed commands read most of the machine, ~/.ssh included; block secret files yourself
  • The sandbox covers shell commands only; the Read/Edit tools, MCP servers and hooks run outside it

An AI coding agent that asks before every command trains people to click “yes” without reading, which is worse than not asking at all. Anthropic’s answer in Claude Code is an OS-level sandbox: the agent runs commands freely inside a file and network boundary you define, and asks only when it wants to step outside.

Facts checked against the Claude Code sandboxing docs, Anthropic’s engineering post (October 2025) and the sandbox-runtime README, as of October 2026. Setting names and defaults change between versions; check the docs for the version you run.

Why a sandbox beats approval prompts

Approval prompts rely on human attention, which runs out fast. A sandbox hands the decision to the operating system: inside the boundary, go ahead; outside it, the OS refuses, whether the agent was fooled by prompt injection or simply wrote a bad command.

Anthropic reports that in its internal usage, sandboxing reduced permission prompts by 84% (the post does not say how this was measured).

Worked example: what 84% means in a day

Suppose a session raises 100 approval prompts at 5 seconds each (assumed figures for the arithmetic):

  1. Without the sandbox: 100 × 5 = 500 seconds, about 8 minutes of babysitting
  2. With 84% fewer: 100 × (1 − 0.84) = 16 prompts
  3. Time spent: 16 × 5 = 80 seconds

More important than the time: the 16 that remain are the ones worth asking about, such as a new domain or a write outside the project, so people still have the attention to read them.

How it works

SystemMechanismInstall
macOSSeatbelt (built in)Nothing
Linux and WSL2bubblewrap, separate namespaces including a network namespacebubblewrap, socat
Native WindowsClaude Code runs commands unsandboxedUse WSL2

Two layers work together:

  • Filesystem. Commands can write only to the working directory, the per-user temp directory and directories you added. Claude Code’s own config files are write-protected even inside the working directory, because a command that could edit them could grant itself permissions or add a hook that runs outside the sandbox
  • Network. Commands have no direct route out. Every connection goes through a proxy on your machine, outside the sandbox, which checks the hostname against your allowed domains. That list starts empty

The docs stress that both are needed. Without network isolation, a compromised agent can send files such as SSH keys out. Without filesystem isolation, it can modify system files to open a network path for itself.

Defaults you need to know

DefaultChange with
WritesWorking dir, temp, added dirsfilesystem.allowWrite, filesystem.denyWrite
ReadsMost of the machine, including ~/.ssh and ~/.aws/credentialsfilesystem.denyRead, credentials
NetworkThrough the proxy; allowed domains start emptynetwork.allowedDomains, network.deniedDomains
EnvironmentInherited from Claude Code, secrets includedcredentials, CLAUDE_CODE_SUBPROCESS_ENV_SCRUB

Turning the sandbox on does not protect your secret files. By default commands can still read ~/.ssh; block it yourself.

Setup on Ubuntu or WSL2

Install the dependencies (Debian/Ubuntu, per the docs):

sudo apt-get install bubblewrap socat

Turn it on in a session with /sandbox; the panel also shows anything missing. Or put it in ~/.claude/settings.json:

{
  "sandbox": {
    "enabled": true,
    "allowUnsandboxedCommands": false,
    "filesystem": {
      "denyRead": ["~/.ssh", "~/.aws", "~/.config/gcloud"]
    },
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org", "registry.npmjs.org", "pypi.org", "files.pythonhosted.org"]
    }
  }
}
  • allowUnsandboxedCommands: false removes the escape hatch where a command that failed in the sandbox is retried outside it (normally via the dangerouslyDisableSandbox parameter, which then goes through the usual approval flow)
  • denyRead stops shell commands reading keys and credentials
  • List only the domains the work actually needs

Check it works: have the agent run touch ~/sandbox-probe (what you type yourself after ! usually runs outside the sandbox, so it proves nothing). On Linux/WSL2 you should get Read-only file system; on macOS, Operation not permitted. If the touch succeeds, delete the file and open /sandbox to see whether it is really on.

Inside the sandboxshell commandschild processesnpm installOutside the sandboxRead/Edit/WebFetchhooks, MCP serversexcluded commands
The sandbox wraps shell commands; the rest follow permission rules

What the sandbox does not cover

This is the most common misunderstanding. The sandbox wraps shell commands only (Bash, PowerShell, Monitor and their child processes). These run outside it:

  • Built-in tools. Read, Edit, Write, WebFetch and WebSearch follow permission rules instead. A denyRead entry does not stop the Read tool and allowedDomains does not limit WebFetch; you need permission deny rules as well
  • Other processes Claude Code starts: hooks, local MCP servers, LSP servers and your status line command run with your full access
  • Commands you type after ! in most sessions
  • Commands in excludedCommands, and unsandboxed retries you approve

For one boundary around everything, run the whole agent in a container or VM, or use Anthropic’s open-source sandbox runtime (srt), which wraps any process, including other agents and MCP servers. As of October 2026 it is a beta research preview.

npm install -g @anthropic-ai/sandbox-runtime
srt "curl example.com"        # blocked unless example.com is in ~/.srt-settings.json
srt "cat ~/.ssh/id_rsa"       # Operation not permitted, when ~/.ssh is in denyRead

Security limits

  • The proxy decides on the hostname and does not inspect TLS. Allowing a broad domain such as github.com opens an exfiltration path, and the docs warn about domain fronting as a way around the allowlist
  • Unix sockets. Allowing /var/run/docker.sock effectively hands over the host (the Docker hardening article explains why that socket equals root)
  • Over-broad writes. Write access to .bashrc, .zshrc or a directory on $PATH lets code run outside the sandbox the next time you open a shell
  • enableWeakerNestedSandbox makes it work inside Docker but weakens it considerably; use it only when another isolation layer surrounds it

Common mistakes

  • Assuming keys are safe once the sandbox is on. Default reads include ~/.ssh. Add denyRead or credentials, and better still, keep production keys off the machine the agent works on
  • Putting docker * in excludedCommands because docker fails in the sandbox. That gives the agent docker with your full rights; keep the pattern as narrow as possible
  • Allowing * as a domain to stop the prompts. The whole network layer is gone
  • Testing by typing commands yourself. Commands after ! usually skip the sandbox, so the test lies
  • Running on native Windows and assuming you are protected. There is no sandbox there; use WSL2
  • Forgetting that npm install is a command too. Package install scripts run inside the sandbox, which is good; excluded, they run with your full rights. See the supply chain article

Sources

FAQ

Does it work on Windows?

On native Windows, Claude Code runs commands unsandboxed. For a sandbox, run Claude Code inside WSL2, which uses bubblewrap like Linux.

Why can't ssh or my database client connect even though the host is in allowedDomains?

The sandbox routes traffic through a proxy via HTTP_PROXY/HTTPS_PROXY. Tools that ignore those variables, such as plain ssh and most database drivers, cannot connect. UDP and ping cannot leave either.

With the sandbox on, is it safe to leave the agent running alone?

It cuts risk a lot, but the docs themselves say it is not a complete isolation boundary. For a boundary around the whole process, run the agent in a container, a VM or the sandbox runtime.

Commands and settings were tested on sample systems. Try them on a test machine before production.