Guide
Security & isolation model
Defense in depth — no single control below is sufficient by itself. This guide summarizes what SECURITY.md documents in full; treat that file, not this page, as the authoritative source.
One pod, two containers, sharing volumes
Every sandbox session gets its own Kubernetes pod with two containers. The sandbox container runs agent-generated code: non-root (UID 1001), runAsNonRoot: true, every Linux capability dropped (capabilities.drop: [ALL]), a read-only root filesystem, and allowPrivilegeEscalation: false. The sidecar container runs the HTTP API and storage sync, as root, with only SYS_PTRACE/SYS_ADMIN added (never inherited by the agent code it execs into).
Per-exec network isolation is the real network boundary
Not NetworkPolicy alone. In Kubernetes mode, every /exec runs inside a freshly created, empty network namespace (unshare -n before nsenter) — no interfaces at all, so it structurally can't reach the network or the sidecar's own port, regardless of what any firewall rule says. NetworkPolicy is the second, independent layer: the backstop for everything else in the pod, and the only thing standing between sandboxed code and the network if per-exec isolation is ever explicitly disabled.
Sidecar auth fails closed
Every route but /health requires an X-Sidecar-Auth-Token generated fresh per pod at creation time — never a static, repo-wide value, and never a plaintext pod annotation (it lives in a Kubernetes Secret, requiring a separate RBAC grant to read). If the token isn't configured, protected routes return 503 instead of running unauthenticated.
The sidecar's residual privilege
The sidecar holds CAP_SYS_ADMIN for the pod's entire lifetime, because nsenter's setns()needs it to enter the sandbox container's namespaces. That's a near-root capability — a future bug in the sidecar's own request handling (an auth bypass, path traversal, or command injection) would be equivalent to full CAP_SYS_ADMIN root in the pod. This is inherent to the current nsenter-based design, not an oversight — it's exactly why sidecar auth and NetworkPolicyare the layers that actually matter, not the sidecar's own privilege level.
Multi-tenant isolation
Every hosted-mode route is scoped to the authenticated account at the database layer (SandboxSessionRepository.get_for_account), not filtered after the fact — a session_id belonging to another account and one that never existed both 404 identically, so no route can be used to probe whether a given session exists. Warm-pool pods are fully wiped (workspace, uploads, outputs, skills, in-memory session state) before being handed to a new tenant.
Output redaction is a cosmetic backstop, not a security boundary
The regex-based secret scrubbing in bash_tool.pycatches accidentally-echoed labeled secrets — it won't catch a deliberately evasive agent splitting a value across calls. Don't treat it as a substitute for not giving a sandbox access to a secret it doesn't need.
Known limitations — account for these before you rely on isolation alone
- The docker-compose sidecar mounts the host's Docker socket, a full host-root escape primitive if it's ever compromised — local dev only, never a production deployment target.
- The docker-compose (local dev) runtime doesn't give sandboxed commands the same per-exec network isolation the Kubernetes runtime does.
- The command allowlist (
SANDBOX_ALLOWED_COMMANDS) cannot meaningfully restrict what a general-purpose interpreter (python3, node) does once it's itself allowed. - No per-container PID limit expressible in a pod spec — requires a node-level kubelet setting (see the Kubernetes deployment guide).