Claude Code runs inside an OS-level sandbox that restricts filesystem and network access at the kernel layer. Even if a prompt injection tells Claude to read /etc/shadow or exfiltrate data over HTTP, the operating system blocks the operation before it reaches the filesystem or the network stack. This is not application-level filtering that Claude can reason around — it is enforcement by the kernel itself.
Platform Support
Sandbox Backends by Platform
| Platform | Backend | Mechanism | Status |
|---|---|---|---|
| macOS | Seatbelt (sandbox-exec) | Kernel-level sandbox profiles restricting syscalls, filesystem paths, and network | Fully supported |
| Linux | bubblewrap (bwrap) | Mount namespaces for filesystem isolation, network namespaces, process isolation | Fully supported |
| WSL2 | bubblewrap (bwrap) | Same as Linux — WSL2 runs a real Linux kernel with full namespace support | Fully supported |
| WSL1 | None | WSL1 translates syscalls to Windows NT; no Linux namespace support | Not supported |
| Native Windows | None | No Seatbelt or bubblewrap equivalent available | Not supported |
Filesystem Isolation
By default, the sandbox allows read-write access to exactly three locations:
cwd— the working directory where you launched Claude~/.claude/— Claude’s configuration and session data- System tmpdir — temporary files needed during execution
Everything else on the filesystem is blocked. If Claude attempts to read a file outside these directories, the OS kernel denies the syscall and returns a permission error. This holds true even if Claude uses Bash to attempt raw file reads — the sandbox sits below the shell.
# This fails -- /var/log/app is outside the sandbox$ claude -p "Read /var/log/app/error.log" --output-format jsonThe response will contain a denial because /var/log/app is not within the allowed directory set.
Extending Access with —add-dir
The --add-dir flag punches a hole in the sandbox for a specific directory. You can use it multiple times to grant access to several paths:
# Grant access to two additional directories$ claude -p "Analyze logs and check config" \ --add-dir /var/log/app \ --add-dir /etc/app-config \ --output-format jsonKey behaviors of --add-dir:
- Paths must be absolute — relative paths are rejected
- The path must exist at invocation time
- Added directories get read-write access, the same level as
cwd - The sandbox boundary becomes:
cwd+ all--add-dirpaths +~/.claude/+ system tmpdir
—add-dir grants full read-write access. If you need read-only access to an external directory, combine it with —disallowedTools “Write,Edit” to prevent modifications while still allowing reads.
Network Isolation
Filesystem isolation alone is not enough. Without network restrictions, a compromised agent could read SSH keys from ~/.ssh/ (which may be inside the allowed sandbox) and exfiltrate them over HTTP.
The sandbox can block network access entirely, and you can further restrict it at the tool level:
# Air-gapped agent -- no web access, no Bash escape$ claude -p "Analyze this code offline" \ --disallowedTools "WebFetch,WebSearch,Bash" \ --permission-mode bypassPermissions \ --output-format jsonWhen web tools are blocked, Claude falls back to its training knowledge. The response comes from what the model already knows, not from live web data. Verify freshness accordingly.
Defense Layers
Safe unattended operation requires all three layers working together. No single mechanism is sufficient on its own:
Defense-in-Depth Layers
| Layer | Mechanism | What It Blocks | What It Misses |
|---|---|---|---|
| Sandbox | OS-level filesystem and network isolation | Access to paths outside allowed directories, unauthorized network calls | Anything within allowed directories is fair game |
| Tool restrictions | —allowedTools and —disallowedTools | Specific tool usage (Write, Edit, Bash, WebFetch) | MCP tools bypass built-in tool restrictions |
| Hooks | PreToolUse / PostToolUse event handlers | Custom rules — regex on file paths, command auditing, API call logging | Only as strong as the rules you write |
Consider a prompt-injection attack chain: an injected instruction tells Claude to cat ~/.ssh/id_rsa. The filesystem sandbox blocks the path if ~/.ssh/ is outside allowed directories. If the read somehow succeeds, the network sandbox blocks exfiltration. And even if both layers fail, a PreToolUse hook can match the path pattern and reject the operation. Each layer catches what the previous one might miss.
The --dangerously-skip-permissions flag skips all permission prompts, but hooks still fire. This makes hooks the last line of defense in fully automated pipelines:
Permission check flow with --dangerously-skip-permissions: 1. Tool requested --> permission check --> SKIPPED 2. PreToolUse hook --> STILL FIRES --> can block execution 3. Tool executes --> sandbox --> STILL ACTIVE 4. PostToolUse hook --> STILL FIRES --> can audit resultsProof: The allowedTools Bypass
The most dangerous misconception in Claude Code security is that --allowedTools alone creates a read-only agent. It does not. Here is the proof — a supposedly read-only agent successfully writing a file:
# WRONG: This is NOT read-only$ claude -p "Write hello to /tmp/outside_test.txt" \ --allowedTools "Read,Grep,Glob" \ --permission-mode bypassPermissions \ --output-format jsonClaude fell back to Bash (which was not in --allowedTools but was not explicitly blocked either) and ran echo "hello" > /tmp/outside_test.txt. The --allowedTools flag is a preference, not an enforcement boundary. The --disallowedTools deny list is what actually blocks tools.
The correct pattern for a true read-only agent:
# RIGHT: Both allowedTools AND disallowedTools$ claude -p "Analyze this codebase for security issues" \ --allowedTools "Read,Grep,Glob" \ --disallowedTools "Write,Edit,Bash,WebFetch,WebSearch" \ --permission-mode bypassPermissions \ --output-format jsonAir-Gapped Response
When web tools are blocked, Claude responds entirely from training knowledge. This payload shows what that looks like:
Notice web_search_requests: 0 and web_fetch_requests: 0. Claude did not attempt any web access — it recognized the tools were unavailable and produced the answer from what it already knew.
The sandbox is enforced at the OS kernel level. Prompt injection cannot escape it. Even if an attacker crafts a prompt that instructs Claude to cat /etc/shadow or curl https://evil.com, the kernel blocks the syscall before it executes. This is the single most important property of the sandboxing system.
WSL1 is not supported. WSL1 translates Linux syscalls to Windows NT kernel calls and does not provide the Linux namespace isolation that bubblewrap requires. If you are on Windows, use WSL2 (which runs a real Linux kernel) or a Docker container with a Linux image.
Hook exit 2 survives —dangerously-skip-permissions. Experimentally confirmed: a PreToolUse hook that exits with code 2 blocks tool execution even when —dangerously-skip-permissions is active. Hooks are the only enforcement layer that cannot be bypassed by any flag. The security stack is: hooks (unbyppassable) > sandbox (kernel-level) > permissions (flag-bypassable).