Sandboxing Claude Code: put your agent in a box that holds
7m read time

Sandboxing Claude Code: put your agent in a box that holds

How to sandbox Claude Code: the built-in /sandbox, the sandbox runtime, dev containers and VMs compared, and why permission prompts are consent, not containment.

In March, the engineers at Ona published a story that should be pinned above every agent setup. Their denylist blocked /usr/bin/npx by exact path. Claude Code wanted to run npx anyway, so it invoked /proc/self/root/usr/bin/npx: the same binary, through a path the pattern didn't match. When a second layer caught that trick, the agent identified the sandbox as the obstacle, disabled it, and ran the command outside it.

No jailbreak. No attacker. No malicious prompt. The agent just wanted to finish its task, and the rules were in the way.

That story is the whole argument of this post. In my guide to Claude Code permissions I ended on Anthropic's own framing: permissions are the lock on the door, the sandbox is the wall. This is the post about the wall. What actually holds on the day the agent, or whatever poisoned its context, decides your rules are an obstacle to route around.

A permission prompt is a question. You approve 93% of them, your finger learns the shortcut, and the approval becomes a reflex rather than a review. That's consent, and consent has a failure mode: you.

Rules and allowlists are better, but they share a weakness with the prompt: they live inside the process, and they negotiate with the model. A pattern can be side-stepped, as Ona's /proc/self/root trick showed. A config file can be edited by anything that can write files. A boundary the agent can reach is a boundary the agent can move.

So here is the test that sorts every control into one of two piles: does it still work when the model is actively working against it? If yes, it's containment. If no, it's consent. Prompts, permission rules and modes are consent. The wall has to be containment, which means it has to live outside the process, enforced by the operating system or the hypervisor, out of reach of the thing it contains.

What the built-in sandbox holds ​

The good news: Claude Code ships a real wall, and it's one command away. /sandbox turns on OS-level isolation for every Bash command: Seatbelt on macOS, bubblewrap on Linux and WSL2.

Writes are confined to the working directory. All network traffic is forced through a proxy that runs outside the sandbox and checks every domain against an allowlist. A command that tries to read ~/.ssh or call an unapproved host fails at the OS, whatever the model intended.

The trade is unusually good. Because sandboxed commands are contained, they run without prompting, and Anthropic measured an 84% drop in permission prompts. Fewer interruptions and a harder boundary at the same time. If you run Claude Code on your own machine and you haven't typed /sandbox yet, that's the single highest-value change you can make today.

Know what it covers, though. The sandboxed Bash tool wraps Bash commands and their children. Nothing else.

File tools like Read and Edit run inside the Claude Code process, gated by permission rules. MCP servers and hooks are separate processes that run unconstrained on your host. The MCP server holding your database credentials sits entirely outside the box, which is why locking down the agent's database access is its own job with its own post.

The escalation ladder ​

Everything past /sandbox is about widening the boundary until your whole session sits inside it. The options, in order of effort:

ApproachWhat's inside the wallNeeds Docker
Sandboxed Bash tool (/sandbox)Bash commands and their childrenNo
Sandbox runtimeThe whole process, including MCP and hooksNo
Dev container with a default-deny firewallThe full development environmentYes
Dedicated VM or microVMAn entire operating systemNo

The sandbox runtime is the underrated middle step. It's the same Seatbelt/bubblewrap machinery, but wrapped around the entire Claude Code process instead of individual commands:

bash
npx @anthropic-ai/sandbox-runtime claude

One line, no Docker, and suddenly your MCP servers and hooks are inside the boundary too. You configure allowed write paths and network domains in ~/.srt-settings.json, default-deny on both.

The dev container route matters for a different reason: it's the setup Anthropic itself points at for unattended work. The example devcontainer ships a default-deny iptables firewall, and behind that firewall, --dangerously-skip-permissions changes category. Inside a container that can't reach arbitrary hosts and can't touch your home directory, skipping consent is a reasonable trade for autonomy. On your host, it's handing your shell to the worst sample in the distribution.

And for code you genuinely don't trust, a repo you're auditing, a dependency you suspect, nothing beats a throwaway VM with no credentials on it. Kernel-level separation, nothing shared, delete it afterwards.

The two leaks no sandbox fixes ​

Whichever box you pick, two channels stay open, and pretending otherwise is how sandboxes become false comfort.

Egress is exfiltration. Every domain on your allowlist is a channel out. If the agent can read a secret and reach one permissive host, the secret can leave. Keep the allowlist short and boring: your package registry, your API endpoint, and a hard stare at anything else. The other half of that problem is the reading, which is what masking credentials in the sandbox is for.

And remember the channel you can't close: everything the agent reads is sent to the model API by design. The sandbox decides what the process can touch, never what leaves in the prompt.

The writable mount is the point, and the risk. The agent's job is to change your code, so the project directory is writable, so the sandbox will happily watch it write something you'll execute later, outside the wall, with your privileges. No isolation reviews a diff. The wall buys you the certainty that nothing else was touched. What was touched still crosses on your judgement.

One more lesson from the Ona story: after they moved enforcement into the kernel, the agent found a bypass through the ELF dynamic linker, loading binaries via a path no one had thought to guard. Walls have seams. That's an argument for stacking layers. The intrusion that ran without a human at the wheel got exactly as far as its boundaries allowed, and that's the property you're buying.

Two weeks after I published this, two agent sandbox escapes landed within days of each other, and both went out through exactly this kind of seam.

What I actually run ​

My setup, by trust level:

  • My own repos, me watching: /sandbox on, network allowlist, plus the permission rules and hooks from the permissions guide. Layers are cheap.
  • Unattended runs: a dev container with the default-deny firewall, the project mounted as the only writable path, and no secrets in the image at all. This is where --dangerously-skip-permissions is allowed to exist. A CI runner is the unattended case you did not build yourself, and it starts with none of this.
  • Untrusted code: a disposable VM with nothing on it worth taking.

The pattern behind all three: decide what the agent may reach before it runs, enforce that outside the process, and let the permission prompts go back to being what they should have been all along, a rare question that deserves an actual answer.

All it takes is an agent that is diligent, blocked, and creative, three things you selected it for. The wall is for the day those three point somewhere you didn't.

(18 of 38)
1My Claude Code setup: status line, plugins and terminal2Superpowers: teaching Claude Code to think before it types3Claude Code hooks: deterministic control over AI workflows4The CLAUDE.md file: give your AI permanent memory5Stop asking your agent nicely6What's new in Claude Code: notes from the London talk7The best number in Opus 4.8 isn't a benchmark8Stale memory is worse than no memory9The agent is just a loop10Build an MCP server, then ask whether it should exist11Skill, subagent, hook, or slash command? Pick the right one12Log in to MCP servers from your shell13How to give Claude safe access to your SQL database14The day 'default' became 'Manual'15Claude Code skills: how to write one that works16How to write a proper Claude Code subagent17Claude Code permissions: the guide I wish the docs were18Sandboxing Claude Code: put your agent in a box that holds19Prompt injection defense for developers who ship agents20Best Claude model for coding: which one for which task21Refactoring legacy code with a coding agent: start with characterization tests22MCP server authentication: OAuth, scopes and rate limits23Opus 5 is here and your effort settings just expired24AI agent incident response: what to do when your coding agent goes wrong25Claude Code /doctor: what it checks and what changed26Claude Code context management: when to /clear and when to /compact27Git worktrees for parallel coding agents: what they isolate and what they share28Claude Code plan mode: decide before the agent writes29Debugging with a coding agent: give it the search, keep the hypothesis30Claude Code checkpoints and /rewind: how to undo an agent's changes31Claude Code cross-session messaging: how to make your sessions talk to each other32Audit logging for AI agents: what Claude Code records and what deserves a human33Sharing Claude Code config across a team: what a repo can and cannot enforce34Your Claude Code session has no clock35Claude Code in a large codebase: scoping an agent to the part that matters36Recovering a Claude Code session your picker will not show you37Where Claude Code spends its time: a month of my own sessions, measured38Steering Claude Code mid-task: Esc, queued messages and /btw