Sandbox backends
Este conteúdo não está disponível em sua língua ainda.
Backend overview
Section titled “Backend overview”| Backend | Value | Platform | Availability |
|---|---|---|---|
| Enclosed VM | microvm |
macOS, Linux, Windows (microVM hypervisor) | Probe-gated on the microVM runtime; applies to interactive terminals — the shell runs inside a rig, not on the host |
| Native | native |
macOS (Seatbelt), Linux and WSL2 (bubblewrap) | Probed at boot; Linux refuses a run requiring filename-glob write denies, which bubblewrap cannot enforce |
| None | none |
All | The parse default, the terminal’s default and the fallback when no backend is available or --sandbox off is set |
SandboxBackend.fromName returns none for a null or unrecognized stored value. A stored "docker" maps to native.
microvm is the one backend with a kernel boundary — the workload
genuinely does not run on the host. It is never a silent fallback in either
direction: explicitly requesting it on a host that cannot boot a VM fails
loudly (“I asked for a VM and got my laptop” is the one degradation the
enclosure cannot afford), and only an unspecified default steps down to
native, with a log line saying so. The session’s actual backend is reported
back to the client and badged, so a terminal can never claim to be enclosed
while running on the host. Lifecycle, images and egress for the VM itself are
covered by the rigs reference.
Windows has no native sandbox. NativeSandboxAdapter.probe returns available: false on every host that is not macOS or Linux/WSL2. Agent dispatch on Windows therefore uses none. The microvm backend is separate: it is probe-gated on the microVM runtime and applies to interactive terminals and rigs, not to wrapping bash on the host.
Probe results
Section titled “Probe results”The probe is what gates enforcement. It runs once, at server boot.
| Host | available |
Note / hint |
|---|---|---|
| macOS | true |
sandbox-exec (Seatbelt) — namespace isolation, no kernel boundary |
Linux or WSL2 with bwrap and socat |
true |
bubblewrap — namespace isolation, no kernel boundary |
| Linux or WSL2 missing either tool | false |
requiresInstall: true, with an install hint naming the missing packages |
| Any other platform | false |
Native sandbox is not offered on this platform |
The server logs the outcome on one startup line: agent sandbox ON plus the note, no OS-native agent sandbox on this host plus the hint, or agent sandbox DISABLED by --sandbox off. GET /healthz carries no sandbox field.
--sandbox / CC_SERVER_SANDBOX accepts on (default) or off. It is an opt-out, not an enable: on means “wrap when the host offers a backend”, never “require one”.
Native backend
Section titled “Native backend”macOS: Seatbelt
Section titled “macOS: Seatbelt”Uses Apple’s Seatbelt sandbox mechanism (the same system used by macOS App Sandbox).
The generated profile is permissive by default ((allow default)) and carves out denies, because a fully deny-by-default profile makes an interactive shell unusable — there are too many dyld, XPC and Mach calls to enumerate. The practical consequence is that an agent can still read most of the host; your registered checkouts are protected by explicit write and rename denials, not by invisibility.
- Filesystem reads: denied for the secret paths (
~/.ssh,~/.aws,~/.gnupg,~/.config/gh,~/Library/Keychainsand the rest of the deny-read set) - Filesystem writes: reset to deny, then explicit subpath allows, with the mandatory-deny paths (shell rc files,
.git/hooks, Claude config, protected checkouts) blocked even inside a writable root.file-write-unlinkis denied on every denied path and its ancestors, so a rename cannot bypass a write-deny - Process:
process-execdenies for the always-dangerous binaries, plus a block on executing anything from a writable directory - Network: restricted to the in-process proxy ports plus DNS — loopback is not blanket-allowed and Unix sockets are not allowed. The proxies enforce the per-session domain allowlist (see Network egress)
- Configuration: profiles are generated per launch from the
SandboxSpec
Requirements:
sandbox-exec(/usr/bin/sandbox-exec), part of every macOS install — no additional tooling required
Linux: bubblewrap
Section titled “Linux: bubblewrap”Uses bubblewrap (bwrap) to create mount and PID namespaces.
- Filesystem: creates a mount namespace with only the bound paths visible
- Network: runs in an unshared network namespace; traffic reaches the host proxies through
socatUnix-socket bridges - Process: PID namespace isolation
Requirements:
bubblewrapandsocatinstalled (sudo apt-get install bubblewrap socaton Debian/Ubuntu)- Kernel namespace support (standard on modern Linux)
Bubblewrap can shadow an existing path but cannot forbid a new .env,
*.key or *.pem file inside a writable bind. The standard dispatch policy
requires these filename-glob denies, so native agent dispatch currently
fails closed on Linux/WSL2 even when the probe finds bubblewrap. It does not
run with a silently weaker policy. A backend with create-time filename rules
is required to restore Linux native dispatch. Disabling sandboxing is an
explicit unconfined opt-out, not an equivalent workaround.
None backend
Section titled “None backend”No sandboxing. The process runs directly on the host with the host’s filesystem, network and process access. ACP and the harness bash tool still get environment sanitization and the command policy; Claude Code through NoSandboxAdapter inherits the host environment. This is what agent dispatch falls back to on a host with no available backend or with --sandbox off and it is the terminal’s default when a client names none.
Server-hosted terminal
Section titled “Server-hosted terminal”The terminal is sandboxed on its own terms, not by the dispatch probe. TerminalSessionService’s defaultBackend is none and cc_server does not override it, so a terminal session runs the host shell unless the client explicitly asks for native — the desktop’s terminal panel does not.
When native is requested the shell is wrapped through SandboxManager under a fixed terminal profile: $HOME and the session’s working directory are writable, network is open and ~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gh and ~/Library/Keychains are read-denied. Capability gating does not apply — that is a dispatch concern. If the wrap is unsupported, the session fails instead of showing a native badge on a host-shell PTY.
Network egress
Section titled “Network egress”When a sandbox is launched, outbound network access is enforced by in-process proxies, not by kernel firewall rules. On a host with no available backend none of this applies and a spawned process reaches the network exactly as the server user would.
- The sandbox runtime runs an HTTP/HTTPS proxy and a SOCKS5 proxy on
127.0.0.1inside thecc_serverprocess, one pair per restricted session (http_proxy.dart,socks_proxy.dart). Concurrent runs do not share a pair. - Sandboxed processes receive proxy environment variables (
HTTP_PROXY/HTTPS_PROXY→ the HTTP proxy,ALL_PROXY→ the SOCKS proxy;GIT_SSH_COMMANDroutes git-over-SSH through the SOCKS proxy). On Linux the network namespace is unshared, so the proxies are the only way out; on macOS the sandbox shares the host loopback and talks to them directly. - Each connection’s target host is matched against the session’s domain allowlist; anything else is denied. Two wildcard forms, each with exactly one
*: a leading*.example.commatches any subdomain (includinga.b.example.com) but not the apex itself; a middle-labelbedrock.*.amazonaws.commatches exactly one label (bedrock.us-east-1.amazonaws.com, notbedrock.evil.attacker.amazonaws.com). - The allowlist is resolved per session from the curated baseline (
kBaselineAllowedDomains: LLM providers, Git hosting, package registries), plus GitHub domains when the agent has GitHub or push capabilities, plusSandboxSpec.egressAllowlist. The resolver also accepts ticketing-provider domains when the agent can call ticketing, but the dispatch path never passes that list (or extraegressAllowlistentries), so in practice every run gets the baseline plus the GitHub domains its capabilities earn. - A fixed deny list (
kBaselineDeniedDomains: cloud metadata endpoints, telemetry sinks) takes precedence over all allows. networkEnabled: false(or an agent without the network capability) still starts the per-session proxies, but the allowlist is empty so every outbound connection is refused.
SandboxSpec
Section titled “SandboxSpec”The specification passed to SandboxPort.launch:
| Field | Type | Description |
|---|---|---|
sessionId |
String |
Stable session identifier (typically the space id) |
workspaceId |
String |
Owning workspace id |
agentId |
String? |
Agent currently bound to the sandbox, if any |
bindMounts |
List<SandboxBindMount> |
Host→guest mounts; the sandbox sees only the host paths listed here |
networkEnabled |
bool (default true) |
Combined with the agent’s network capability. When off, per-session proxies still start but the allowlist is empty so every outbound connection is refused |
egressAllowlist |
List<String> (default []) |
Extra allowed egress domains; wildcards supported (*.example.com, plus a one-label middle form). Dispatch never populates this, so a wrapped run gets the curated baseline plus GitHub domains its capabilities earn |
guestWorkdir |
String? |
Default working directory inside the sandbox; individual exec calls can override it |
mode |
Mode (default chat) |
Carves the filesystem write rules: chat keeps bind mounts writable; review, plan and orchestrate are read-only |
capabilities |
AgentCapabilities (default safeDefault) |
Derives the network on/off decision and GitHub egress domains. Ticketing domains are a resolver argument the dispatch path never supplies |
protectedPaths |
List<String> (default []) |
Host paths that must never be writable inside the sandbox, in any mode |
runnerStateDirs |
List<String> (default []) |
Host directories the runner keeps its own state in, writable in every mode (a Control-Center-managed CLAUDE_CONFIG_DIR is the current one) |
execGrantRoots |
List<String> (default []) |
Operator-approved directory trees that re-open the writable-dir exec block on macOS. Linux ignores them — bwrap has no $HOME exec block to re-open |
protectedPaths holds the original registered repo checkouts, resolved per dispatch from the workspace’s repos. Agents work exclusively in per-conversation copy-on-write worktrees, so the originals must never be writable from inside the sandbox — on macOS, Seatbelt’s blanket $HOME write allowance would otherwise leave them writable via bash. They join the secrets deny-write set in every mode, chat included.
Every deny-read and deny-write path is emitted in both its literal spelling and its symlink-resolved one. The kernel matches a deny rule against the resolved path, so a rule written against a symlinked path never fires — and on macOS that is the common case rather than the exotic one, since /tmp resolves to /private/tmp and /var/folders/… to /private/var/folders/…. Entries containing * or ? are treated as globs and left alone; a path that does not resolve is kept as written.
SandboxBindMount
Section titled “SandboxBindMount”| Field | Type | Description |
|---|---|---|
hostPath |
String |
Absolute path on the host |
guestPath |
String |
Path inside the sandbox (with the native sandbox this equals hostPath; retained for adapter symmetry) |
readOnly |
bool (default false) |
Mount read-only when true |
SandboxHandle
Section titled “SandboxHandle”Returned by SandboxPort.launch:
| Field | Type | Description |
|---|---|---|
sessionId |
String |
Session identifier |
backend |
SandboxBackend |
Which backend is running |
state |
SandboxState |
Lifecycle state |
error |
Object? |
Set when state is error |
details |
Map<String, Object?> |
Adapter-private bookkeeping (working directory, PID, profile path, etc.) |
SandboxState
Section titled “SandboxState”| State | Description |
|---|---|
created |
Spec created but the VM has not started yet |
warm |
Running and ready to exec commands |
active |
Currently executing a command |
suspended |
Paused / checkpointed to disk to save RAM |
destroyed |
Torn down; the handle is no longer valid |
error |
Launch failed — see SandboxHandle.error |
Sandbox events
Section titled “Sandbox events”| Event type | Description |
|---|---|
stdout |
Standard output from the sandbox |
stderr |
Standard error from the sandbox |
exit |
Sandbox process exited (carries exitCode) |
starting |
Sandbox is starting up |
ready |
Sandbox is ready to accept exec calls |
killed |
Process killed by the host (OOM, timeout, manual stop) |
violation |
A sandbox policy was violated (carries a SandboxViolation) |
SandboxViolation
Section titled “SandboxViolation”| Field | Type | Description |
|---|---|---|
action |
String |
The denied verb (e.g. file-read*, network-outbound) |
target |
String |
Path or host that was the target of the denied operation |
suggestedCapability |
String? |
The capability flag that would allow this action (e.g. canCallGitHubApi) |
raw |
String? |
Raw log line, useful for debugging |
See also
Section titled “See also”- Sandbox security
- Sandbox policies
- Guardrails
- cc_server CLI: the
--sandboxflag