Type /sandbox in your Claude Code session right now.
If you have never run it, every bash command Claude runs has the same access your terminal does. The Claude Code sandbox is off until you turn it on. The Anthropic docs say so.
Sandboxing is OS-level isolation, the same class of tech Chrome uses on each tab. I ran /sandbox for three questions: Does it work? What does it protect? Can I unalias --dangerously-skip-permissions?
Results:
Filesystem writes are kernel-blocked. Write outside your working directory and you get
Operation not permittedat the syscall level. No dialog to click through.Network isolation runs through a local proxy, not only kernel blocking. Well-behaved HTTP clients route through a localhost proxy that checks the allowlist and returns
CONNECT 403for denied domains. Tools that ignore proxy env vars fall through to a Seatbelt backstop that blocks non-loopback traffic at the socket layer.Sandboxing is opt-in. You have to run
/sandboxand pick a mode. If you have not, your sandbox is off.sandbox.denyReaddoes not stop Claude's Read tool. Sharpest finding. In the runs I measured, a setting that looks like a read perimeter has no effect on Read. Verified with headless Claude and stream-json tool traces. The fix is a separate permission rule. Docs now say Read/Edit/Write use the permission system rather than the sandbox; Experiment 5 shows that in practice.
Finding 4 was the surprise.
What the docs don't tell you
Docs cover enabling /sandbox, modes, and config keys. These four points are what I hand teammates. Some overlap the docs; the value is measured behavior.
Opt-in. If you have never opened the
/sandboxpanel (or setsandbox.enabled), bash is unsandboxed. Soft fallback: missing Linux deps print a warning and run unsandboxed unlessfailIfUnavailableis true.sandbox.denyReadis not a Read-tool perimeter. Bash-layer denies do not stop the built-in Read tool in the headless runs below. Mirror secret paths withpermissions.denyRead(...)rules (and treat Edit/Write the same way until you test them).Hardcoded
.gitdenies. Even insideallowWrite, writes to**/.git/configand**/.git/hooks/**fail. That breaksgit init,git clone, and tools that call them (cargo init,uv init) unless those commands are excluded.Escape hatch is on by default. Claude can retry with
dangerouslyDisableSandboxafter a sandbox failure. Set"allowUnsandboxedCommands": falseif you want real enforcement.
For a second policy layer that can block tools before they run, see Claude Code hooks: the complete setup guide (PreToolUse as a hard deny).
Permissions vs sandbox (quick)
Layer | Permissions | Sandboxing |
|---|---|---|
Scope | All tools: Read, Edit, Bash, WebFetch, MCP | Bash (and PowerShell / Monitor per current docs) and the subprocess tree |
Enforcement | Claude Code checks before running the tool | OS kernel for filesystem; local HTTP/SOCKS proxy for network |
On violation | Prompts user for approval |
|
Bypassable by model? | If user approves | No dialog; escape hatch can still unsandbox a retry unless you disable it |
These two layers cover different ground. Treat them as separate controls.
MCP servers and command hooks still run on the host under the built-in Bash sandbox. If you need the whole Claude Code process inside one OS boundary, Anthropic's docs point at the sandbox runtime / containers / VMs comparison, not /sandbox alone. For MCP token and tool surface, see the Claude Code MCP deep dive.
What sandboxing actually is
Without sandboxing, Claude Code's bash tool is just bash. curl can reach any domain your machine can. cat ~/.ssh/id_rsa returns your SSH key. The only gate is a permission prompt you can auto-approve, configure away, or click through.
Sandboxing removes that dialog. The kernel blocks writes outside your working directory. Network traffic goes through a local allowlist proxy; tools that bypass it hit a kernel block on non-loopback traffic. No "do you want to allow Claude to..." prompt. Syscalls fail like any other permission error, and the proxy returns 403 for disallowed hosts.
macOS ships Seatbelt (TrustedBSD MAC). Apple uses it for Chrome renderers, iOS apps, and most system services. You can run it yourself: sandbox-exec -p '...' bash -c 'whatever'. Claude Code wraps that primitive.
Linux uses bubblewrap, the same sandbox Flatpak uses. WSL2 works because it is real Linux. WSL1 does not; bubblewrap needs kernel features WSL1 cannot provide.
Permissions ask yes or no. Sandboxing removes the question. The kernel refuses the call.
Is Claude Code sandbox on by default?
No.
Anthropic's docs say you enable it yourself. Run /sandbox, pick a mode, and you are in. Never run it and the sandbox is off. You can also set sandbox.enabled in user settings for all projects, or enforce it with managed settings.
On macOS it works out of the box; Seatbelt ships with the OS. On Linux or WSL2 install bubblewrap and socat first (sudo apt install bubblewrap socat on Debian/Ubuntu). Current docs also call out Ubuntu 24.04 AppArmor quirks for bwrap user namespaces.
Fallback is soft. If the sandbox cannot start (missing deps, unsupported platform), Claude Code prints a warning and runs unsandboxed. Teams that want a hard gate set sandbox.failIfUnavailable: true so the warning becomes an error.
Two modes. Auto-allow runs sandboxed bash without asking. If a command cannot be sandboxed (excluded tool or unlisted host), it falls back to the regular permission flow. This is the mode that replaces --dangerously-skip-permissions for bash. Docs are explicit:
Auto-allow mode works independently of your permission mode setting. Even if you're not in "accept edits" mode, sandboxed bash commands will run automatically when auto-allow is enabled.
In regular-permissions mode, the sandbox is on but you still approve each command. You see fewer prompts because denied operations fail instead of asking; allowed stuff still prompts. Safety-net mode.
Filesystem and network rules are identical either way. The only difference is whether sandboxed commands get auto-approved.
How it actually works
I grepped the Claude Code binary to see the raw policy. There is a literal comment in the source strings:
; Essential permissions - based on Chrome sandbox policyThe Seatbelt policy Claude Code ships with comes from Chrome's renderer sandbox. It is a Scheme-style rule set:
(version 1)
(deny default (with message "..."))
; File I/O
(allow file-ioctl)
(allow file-read*)
(allow file-read-metadata)
(allow file-write* (subpath "/path/to/cwd"))
; Mach IPC
(allow mach-lookup (global-name-prefix "com.apple."))
(allow mach-priv-task-port (target same-sandbox))
; Network
(allow network-bind (local ip "*:*"))
(allow network-outbound (remote ip "localhost:*"))
; Process info and signals - restricted to same sandbox
(allow process-info* (target same-sandbox))
(allow signal (target same-sandbox))Default is deny. Anything not explicitly allowed is blocked at the kernel. file-write* is scoped to your working directory. network-outbound is localhost-only, so real-internet traffic must go through a proxy outside the sandbox.
That proxy is the other half. On enable, Claude Code spawns an HTTP CONNECT proxy and a SOCKS5 proxy, then sets over a dozen proxy-related env vars inside sandboxed bash, including tool-specific overrides for gcloud, docker, git+ssh, and rsync:
https_proxy=http://localhost:61653
all_proxy=socks5h://localhost:61654
CLOUDSDK_PROXY_ADDRESS=localhost
DOCKER_HTTP_PROXY=http://localhost:61653
GIT_SSH_COMMAND=ssh -o ProxyCommand='nc -X 5 -x localhost:61654 %h %p'(Ports are illustrative from a measured session; yours will differ.)
Two-layer network defense: well-behaved HTTP clients read HTTPS_PROXY and hit the local proxy, which returns 403 for disallowed hosts. Tools that ignore proxy env vars fall through to Seatbelt, which blocks non-loopback traffic; connect() fails with Operation not permitted.
Not every tool is well-behaved. That matters for Experiment 4.
The subprocess tree
Every child inherits the sandbox. If bash runs npm install, which runs a postinstall that runs curl, all of them share the same Seatbelt container. Spawning a child does not escape it. A wrapper that only intercepts the top-level command would miss the children; Claude Code's sandbox sees the whole tree.
The escape hatch
Docs mention an escape hatch: dangerouslyDisableSandbox. When a command fails inside the sandbox (say docker build hits the daemon-socket block), Claude can retry outside it after a user permission prompt.
I pulled the exact text Claude Code injects into the system prompt when sandboxing is on. It lives in the binary as a nested string array. Wording below is exact; bullet and paragraph layout is mine:
You should always default to running commands within the sandbox. Do NOT attempt to set dangerouslyDisableSandbox: true unless: the user explicitly asks you to bypass sandbox, OR a specific command just failed and you see evidence of sandbox restrictions causing the failure. Evidence of sandbox-caused failures includes: "Operation not permitted" errors, Access denied to specific paths, Network connection failures to non-whitelisted hosts, Unix socket connection errors. When you see evidence of sandbox-caused failure: Immediately retry with dangerouslyDisableSandbox: true (don't ask, just do it). Briefly explain what sandbox restriction likely caused the failure. This will prompt the user for permission. Do not suggest adding sensitive paths like ~/.bashrc, ~/.zshrc, ~/.ssh/*, or credential files to the sandbox allowlist.
"(don't ask, just do it)" is a direct string in the binary. I did not make it up.
Claude Code trains the model to retry failing commands outside the sandbox without asking. That is the escape-hatch behavior some users have noticed and been confused by.
A user on r/claudecode caught it in the wild: Claude Code with npx on the denylist and bubblewrap enabled, asked for the npx version. Denylist blocked the first try. The agent found /proc/self/root/usr/bin/npx (same binary, different path) and tried again. When the sandbox caught that too, it talked itself into disabling the sandbox and running npx outside it.
"Don't ask, just do it."
Turn it off with "allowUnsandboxedCommands": false. The dangerouslyDisableSandbox parameter gets ignored, and the system prompt changes:
All commands MUST run in sandbox mode. The dangerouslyDisableSandbox parameter is disabled by policy.
Want real enforcement? This setting matters. Without it, one failed command is enough for Claude to retry outside the sandbox.
For how the system prompt is structured and what else rides in that payload, see Inside Claude Code's system prompt.
The experiments
Bash-layer experiments used @anthropic-ai/sandbox-runtime, the npm package Anthropic publishes and Claude Code vendors. srt (its CLI) is a good stand-in for bash-layer behavior even if it is not the same binary.
Experiment 5 used headless Claude Code with custom settings and --output-format stream-json tool traces. Those findings are actual tool behavior, not an approximation.
Experiment 1: filesystem perimeter
Question: what can you read and write from inside the sandbox by default?
Setup: a fresh temp directory as the working directory. A fake SSH key at ~/.sandbox-exp1-fake-ssh/id_rsa containing junk text. A sibling project outside the work dir. Sandbox policy: allowWrite: [$WORK], no denyRead, default everything else.
Writes:
Target | Result |
|---|---|
| ALLOWED |
| ALLOWED |
Sibling project directory | BLOCKED: |
| BLOCKED |
| BLOCKED |
| BLOCKED |
Every write outside the sandbox returned Operation not permitted at the syscall level. sudo, chmod, owning the file: none of it matters. Denial sits above the file-mode check. Writes are locked down.
Reads:
Target | Result |
|---|---|
| ALLOWED |
| ALLOWED |
| ALLOWED |
Fake SSH key (chmod 600) | ALLOWED |
| ALLOWED |
| ALLOWED |
Every read succeeded: /etc/passwd, the fake SSH key, a full home listing, my real ~/.zshrc. Default read perimeter is the whole computer.
People hear "sandbox" and picture a closed box. Claude Code's is closed for writes; reads are open by default.
Reads stay open unless you add paths to denyRead (bash) and mirror them in permissions for built-in tools. The Chrome-derived policy allows file-read* globally. Want a secrets perimeter? Configure it. Default is not that.
Experiment 2: network isolation
Question: can a tool in Claude's subprocess tree reach a domain not on the allowlist?
Setup: sandbox with allowedDomains: ["example.com"]. Try reaching google.com from curl, wget, python urllib, node fetch. Then try IP literals and hostname tricks.
Results:
Attempt | Result |
|---|---|
| HTTP 200 |
| exit 56, |
| exit 56 - still blocked |
| exit 56 |
| works |
| HTTP 200 |
| blocked, |
| error: fetch failed |
| error: fetch failed |
Two things stood out.
IP literal bypass does not work. Hitting example.com's IP (93.184.216.34) and github.com's IP with a Host: header rewrite both failed with CONNECT tunnel failed, response 403. The proxy checks something curl cannot lie about, not just the hostname.
Node's native fetch() fails even on an allowed domain. That took a while to unpack. Node fetch is built on undici, which ignores HTTPS_PROXY. Inside the sandbox it tries a direct outbound TCP connection; Seatbelt denies it. The error surfaces as fetch failed with an underlying ENOTFOUND because direct DNS fails too.
Every HTTP client inside the sandbox falls into one of three buckets:
Respects proxy env vars. curl, wget, git (https), python (requests, urllib), pip, pnpm, npm, cargo, uv. These get filtered at the local proxy and work fine for allowed domains.
Has a tool-specific proxy var that Claude Code sets. gcloud reads
CLOUDSDK_PROXY_*, docker readsDOCKER_HTTP_PROXY, rsync readsRSYNC_PROXY, git+ssh gets rewritten throughGIT_SSH_COMMAND. Claude Code knows these and sets them all.Ignores everything. Node's native fetch. Anything built on undici without a manual
ProxyAgent. These break inside the sandbox even on allowed domains.
Any tool built on node fetch() will fail silently on domains that should work: some AI CLIs, scrapers, npm-packaged tools. Fix: patch in undici's ProxyAgent with process.env.HTTPS_PROXY, or use a library that reads the env var. Plenty of npm tooling sits in that third bucket.
Experiment 3: the speed tax
Question: does the sandbox slow anything down?
Setup: run the same operations inside and outside the sandbox, 3 iterations each, report the median.
Operation | Direct | Sandboxed | Overhead |
|---|---|---|---|
| 166ms | 287ms | +121ms |
| 189ms | 271ms | +82ms |
| 186ms | 297ms | +111ms |
| 1061ms | 1218ms | +157ms |
| 361ms | 458ms | +97ms |
Pattern: ~80 to 160ms fixed cost per sandbox call. It does not scale with the work. A 10-second npm install and a 166ms echo pay roughly the same overhead, mostly spawning proxies and loading Seatbelt. Per-invocation setup.
Inside real Claude Code the cost should be smaller; proxies stay up and do not respawn per command. I did not measure that path directly. Best guess from what I could time: tens of milliseconds per command.
Real work (compile, test, install) pays once and then runs at full speed. You would only feel it in a script that spawns hundreds of tiny bash calls, and you can usually collapse that loop. I would not turn the sandbox off for speed.
Experiment 4: tools that break
Question: which dev tools break inside the sandbox?
Setup: realistic tasks inside srt with a permissive config (cache dirs in allowWrite, common registries in allowedDomains). Startup worked for everything I tried. Failures showed up when tools did real work:
Task | Result | Cause |
|---|
pnpm install left-pad
works
pnpm respects HTTPS_PROXY
uv pip install requests
works
uv respects proxy env vars
terraform init
works
no git involvement
git init test-repo
BROKEN
Hardcoded .git/config + .git/hooks/* deny
git clone github.com/octocat/Hello-World
BROKEN
Same .git/hooks copy fails
cargo init
BROKEN
Calls git init internally
uv init
BROKEN
Calls git init internally
gh api /zen
BROKEN
TLS cert error: x509: OSStatus -26276
docker build (tiny Dockerfile)
BROKEN
Docker daemon lives outside the sandbox
watchman watch
BROKEN
fsevents needs kernel features unavailable
node fetch api.github.com
BROKEN
Native fetch ignores HTTPS_PROXY
git init being broken was a surprise. I dug in.
Error is could not write config file ... Operation not permitted on .git/config, or cannot copy ... commit-msg.sample on .git/hooks/. Both paths sit inside the working directory (allowWrite). So why fail?
I probed paths and found two patterns hardcoded-denied at any depth: **/.git/config and **/.git/hooks/**. allowWrite cannot override them.
Reason is clear once you see it. .git/config can set [core] editor = ... or [alias] log = !rm -rf ~, so the next git log runs attacker code. .git/hooks/pre-commit is even more direct. The sandbox is right to block both.
Cost: you cannot run git init, git clone, cargo init, or uv init inside the sandbox unless you mark them excludedCommands. The hardened config at the end of this post does that.
When I first hit this I found no mention in public Anthropic docs. The current sandboxing guide now documents protected .git hooks/config. Turn sandboxing on, run git init, and you still feel it if those commands are not excluded.
Second surprise: gh fails with x509: OSStatus -26276 (macOS keychain "certificate verification failed"). gh is Go; Go's TLS stack on macOS checks the system keychain. The proxy presents its own cert, not in the keychain, so the handshake fails. Any Go tool doing HTTPS directly will hit this. Probably docker CLI, probably parts of aws-cli v2. I only tested gh. Official troubleshooting now lists Go CLIs under TLS failures and recommends excludedCommands (or weaker network isolation with a MITM proxy).
Rules of thumb:
If a tool respects
HTTPS_PROXYand uses a normal TLS stack, it works.If it pins TLS certs through Go's stack, it breaks.
If it scaffolds a fresh
.git/tree, it breaks.
Experiment 5: the bypass
Claim: sandbox.denyRead is documented as restricting what paths Claude Code can read inside the sandbox. Does it protect the Read tool, or only bash?
Setup: three headless Claude Code runs, identical prompts, same file. Only settings change between runs.
Raw tool traces via --output-format stream-json give the literal tool_use calls and tool_result payloads. Final assistant text can mislead (refuse, explain, paraphrase). tool_result is what the tool actually returned.
Target: /Users/abhishekray/.sandbox-demo-deny/data.txt, with a fresh nonce sbx-proof-c4e12f8e25f4 each run. Innocuous filename on purpose (more on that below).
Run 1 is the baseline with no deny rules:
tool_use: Read({file_path: ".../data.txt"})
tool_result: "1\tLine 1: benign file contents\n2\tLine 2: ordinary data\n3\tLine 3: sbx-proof-c4e12f8e25f4\n"Baseline: Read succeeds, returns the contents, the nonce is in the tool_result. As expected.
Run 2 adds sandbox.denyRead: ["/Users/abhishekray/.sandbox-demo-deny"] to the settings:
tool_use: Read({file_path: ".../data.txt"})
tool_result: "1\tLine 1: benign file contents\n2\tLine 2: ordinary data\n3\tLine 3: sbx-proof-c4e12f8e25f4\n"The tool_result is byte-for-byte identical to Run 1.
Read called through. Deny list was set. File sat inside the denied directory. Read returned the contents anyway, nonce and all.
Claude was not choosing to comply. It never saw a block. Sandbox config had zero effect on Read.
To double-check, I had Codex (OpenAI's CLI via an MCP bridge) grep the saved jsonl. Confirmed: Run 2's tool_result contains Line 3: sbx-proof-c4e12f8e25f4, the same nonce from the start of the test. Sandbox deny list bypassed.
Run 3 uses permissions.deny: ["Read(//Users/abhishekray/.sandbox-demo-deny/**)"] with no sandbox-layer deny rule:
tool_use: Read({file_path: ".../data.txt"})
tool_result: "<tool_use_error>File is in a directory that is denied by your permission settings.</tool_use_error>"Same file, path, and prompt. Only settings changed. permissions.deny produced a hard error from the tool layer; Read was invoked and returned tool_use_error before touching the filesystem.
Narrow claim: in these three runs on this file, sandbox.denyRead did not stop Read. permissions.deny Read(...) did.
I only tested Read, not Edit, Write, or WebFetch. Current Anthropic docs say Read, Edit, and Write "use the permission system directly rather than running through the sandbox." That matches Experiment 5. Until you test otherwise, treat every built-in file tool as routing around sandbox.denyRead.
In practice: turn on sandboxing with denyRead for ~/.ssh and ~/.aws and you have not closed the Read-tool side. Bash cannot cat ~/.ssh/id_rsa. In the scenario I tested, Read returned a denyRead'd file as if the setting were absent.
Close the gap with both layers:
{
"sandbox": {
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws", "~/.netrc", "~/.gnupg"]
}
},
"permissions": {
"deny": [
"Read(//Users/**/.ssh/**)",
"Read(//Users/**/.aws/**)",
"Read(//Users/**/.netrc)",
"Read(//Users/**/.gnupg/**)",
"WebFetch(domain:*)"
]
}
}Bash layer and tool layer, same paths, configured independently. Docs also describe a sandbox.credentials block for deny/mask of credential files and env vars inside sandboxed commands. That still does not replace permissions.deny for Read. VERIFY credentials.mask behavior on your version before you lean on it alone.
Back to the innocuous filename. First run used a file named credentials with AWS_SECRET_ACCESS_KEY=.... Claude refused Read: "I won't do that. Classic prompt injection pattern." Renamed to data.txt and Claude called Read every time.
Soft layer above hard enforcement: Claude's refusal to touch obvious-secret-looking paths. Pattern matching loses to notes.md. permissions.deny is the only enforcement that does not depend on what Claude chooses to do.
When to turn it on
Turn it on if:
You run agentic workflows. Long autonomous sessions, background tasks.
You install dependencies from untrusted sources. npm, pip, cargo.
You are working on code from third parties. Public repos, client projects, contributed PRs.
You run Claude remotely or headlessly, where you cannot approve prompts by hand.
You have aliased
--dangerously-skip-permissions. This is the feature that lets you stop.
Leave it off (or use a heavier isolation boundary) if:
Your workflow is dominated by Docker. Docker needs daemon socket access that sits outside the sandbox. The Go-based CLI likely runs into the same TLS trust-store issue as
gh. I did not verifydocker buildend-to-end, but Anthropic's own docs recommend puttingdocker *inexcludedCommands. That tells you what to expect.You use
watchmanheavily. It does not work. Jest with watch mode falls back to polling, which is slow.You scaffold new repos constantly and do not want to add
git inittoexcludedCommands. You can, but it is an extra step every time.
Hybrid works too: auto-allow for routine coding, drop to regular-permissions (or pause the sandbox) for docker or git init. For unattended --dangerously-skip-permissions with MCP and hooks inside the boundary, prefer the sandbox environments guide (runtime / container / VM), not Bash sandbox alone.
A real settings.json
Not the docs' minimal example. Config shaped by the experiments:
{
"sandbox": {
"enabled": true,
"mode": "auto-allow",
"allowUnsandboxedCommands": false,
"filesystem": {
"allowWrite": [
"~/.cache/pnpm",
"~/.cache/pip",
"~/.cargo/registry",
"/tmp/build"
],
"denyRead": [
"~/.ssh",
"~/.aws",
"~/.config/gh",
"~/.netrc",
"~/.gnupg",
"~/.docker"
]
},
"network": {
"allowedDomains": [
"registry.npmjs.org",
"pypi.org",
"crates.io",
"github.com",
"*.github.com",
"api.anthropic.com"
]
},
"excludedCommands": [
"docker *",
"watchman *",
"git init *",
"git clone *",
"cargo init *",
"cargo new *",
"uv init *"
]
},
"permissions": {
"deny": [
"Read(//Users/**/.ssh/**)",
"Read(//Users/**/.aws/**)",
"Read(//Users/**/.netrc)",
"Read(//Users/**/.gnupg/**)",
"Read(//Users/**/.config/gh/**)",
"Read(//Users/**/.docker/**)",
"WebFetch(domain:*)"
],
"allow": [
"WebFetch(domain:github.com)",
"WebFetch(domain:api.github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:docs.anthropic.com)",
"WebFetch(domain:code.claude.com)"
]
}
}allowUnsandboxedCommands: false kills the escape hatch. Without it, Claude retries failing commands outside the sandbox on its own.
denyRead is mirrored by Read(...) in permissions.deny. Same paths, both layers: sandbox for bash, permissions for Read. Experiment 5 in config form. Same idea for WebFetch(domain:*) as deny-all with an explicit allowlist.
github.com in allowedDomains is an accepted risk. Broad domains allow exfiltration via gists and issue comments, but half your tooling talks to github. Matching WebFetch rules keep the tool-layer surface narrower than bash.
Session cost sits next to security. Sizing Max vs API while you lock this down: Claude Code pricing.
FAQ
Is Claude Code sandbox on by default?
No. Run /sandbox and pick a mode, or set sandbox.enabled in settings. Until then, bash has the same access as your terminal.
Does sandbox.denyRead block the Read tool?
Not in the headless runs I measured. sandbox.denyRead constrains sandboxed bash. The built-in Read tool follows permissions rules. Mirror secret paths with permissions.deny Read(...). Docs now state that Read/Edit/Write use the permission system rather than the sandbox.
Sandbox vs permissions: which do I need?
Both, for different jobs. Permissions gate tools before they run (Read, Edit, Bash, WebFetch, MCP). Sandboxing enforces OS limits on bash (and related shell tools) and their children after they start. Neither replaces the other.
Sandbox vs --dangerously-skip-permissions?
--dangerously-skip-permissions skips prompts. It does not add an OS boundary. Auto-allow sandbox mode is the safer way to cut bash prompts while keeping writes and network inside a policy. For fully unattended runs with MCP and hooks inside the fence, Anthropic recommends a container, VM, or sandbox-runtime wrap, not Bash sandbox alone.
Does git init work inside the sandbox?
Usually no. Writes to .git/config and .git/hooks/** are protected/denied even under allowWrite. Put git init * / git clone * (and scaffolders that call them) in excludedCommands, or run those steps yourself outside the sandbox.
Conclusion
Sandboxing held in Experiments 1 and 2. Writes outside the work dir and traffic to unlisted domains failed at the kernel or the proxy; IP literals and Host-header tricks failed too.
Off until you run /sandbox or set sandbox.enabled. If you aliased --dangerously-skip-permissions to dodge prompts, auto-allow is the trade that keeps an OS boundary.
sandbox.denyRead still does not stop Read in the runs I measured. Mirror paths with permissions.deny Read(...). Add hooks that block dangerous Bash before it runs when you want denies the model cannot talk past.
I am turning it on with the settings.json above, not the minimal docs example. Default read access is the whole machine until you close both layers.
