Sandboxing AI Agents, Part 1: Foundations
A sandbox is not a magic box. It is a carefully stated security claim: this process may do these things, may not do those things, and there is evidence when it presses against the boundary.
- No single primitive is a sandbox: Landlock contains filesystem reach, seccomp the syscall surface, namespaces visibility, cgroups resources. Only their composition is a security property.
- "Bits present" is not "confinement works": verify functionally and fail closed rather than silently downgrade.
- Name the ceiling: shared-kernel confinement contains accidents and most prompt injection, but is a different category from a microVM with its own guest kernel.
An AI coding agent is a strange security subject. It is not exactly malware, because you invited it in. It is not exactly a normal developer tool, because it reads untrusted project text and then chooses commands. It is not exactly a compiler, because it can browse your repository, run package managers, inspect environment variables, invoke shells, and decide that the next step is a network request. Sandboxing is the discipline of making that power conditional, and it is the confinement pillar of an auditable workspace: the place where you can say the agent physically could not reach certain resources, provably.
The argument of this post is narrow and, I think, defensible: sandboxing an autonomous agent is a containment problem with a concrete threat model (untrusted code execution, data exfiltration, privilege escalation, resource exhaustion) and it is solved by composing operating-system primitives behind defense-in-depth and fail-closed defaults, not by toggling a single flag. No one primitive is a sandbox. A filesystem allowlist does nothing about network egress; a network namespace does nothing about a fork bomb; a syscall filter does nothing about reading ~/.aws/credentials. Each primitive contains exactly one axis of authority. Only their composition is a security property worth the name.
The basic definition is simple: a sandbox is an enforcement boundary around a workload. The boundary gives the workload some capabilities and withholds others. A good sandbox answers four questions: what object is confined, which operations are allowed, who enforces the rule, and how violations are observed. If any of those are vague, the word "sandbox" is marketing rather than a security property.
If you are new to operating-system security, read "capability" as "permission to do one concrete thing." A process does not need a philosophical right to "use the machine." It needs specific handles: read this directory, write that checkout, open this network connection, create at most this many child processes, and run for this much time. A sandbox is the machinery that makes those handles smaller than the user's normal shell.
A plain-language mental model
Imagine lending a contractor a room in your office. A weak arrangement says "please do not open the file cabinets." A sandboxed arrangement changes the room: the cabinets are not there, the phone can only dial approved numbers, the power strip has a breaker, and the door log records who entered and what they carried out. The contractor may still do useful work, but the room no longer has the full authority of the building.
For software, the "room" is the process environment. Filesystem rules decide which cabinets exist. Network rules decide which numbers can be dialed. Process rules decide whether the workload can inspect or signal other programs. Resource rules decide how much power it can draw. Audit rules decide what evidence remains after the work finishes.
AI agent sandbox threat model
Sandboxing starts with an adversary model, not with a tool. For AI agents, the most common adversary is not a person typing exploit code directly. It is a chain: repository content, dependency metadata, test output, package install scripts, web pages, generated code, and the model's own tool choices. Any of those can cause the agent to execute a command that the human never intended.
A practical threat model has three levels. First is accidental damage: a command writes outside the project, deletes state, consumes all memory, or sends data to the wrong host. Second is prompt-injected behavior: untrusted text instructs the agent to read secrets, bypass policy, or exfiltrate evidence. Third is hostile code execution: a dependency, test, binary, or generated program intentionally attacks the sandbox itself.
Those levels require different boundaries. Worktree isolation handles accidental file collisions between parallel agents. Kernel isolation handles ordinary process abuse. Network egress policy handles exfiltration. MicroVM isolation handles the uncomfortable case where the workload may be carrying a kernel exploit. One tier cannot honestly claim all of them unless it actually implements all of them.
Capabilities, not vibes
A sandbox policy is best understood as a capability table. The process may read these paths, write those paths, open these sockets, fork this many children, allocate this much memory, and run for this much time. Everything else is denied. This is the same mental model as object capabilities: possession of a handle authorizes a specific operation, and absence of the handle means no ambient permission.
Ambient authority is the default danger of a developer shell. Your shell can usually read your SSH keys, cloud credentials, browser profile, package-manager tokens, local databases, and the full repository. When an agent inherits that shell, the agent inherits that authority. A sandbox tries to replace ambient authority with named authority.
| Capability | Bad default | Sandboxed form | Failure prevented |
|---|---|---|---|
| Filesystem read | home directory | project worktree plus selected read-only system paths | secret discovery |
| Filesystem write | any path user can write | worktree only | host damage, dotfile poisoning |
| Network | full internet | deny, or explicit egress allowlist | data exfiltration, callback channels |
| Process | same PID space | private PID namespace, signal limits | host process inspection or signaling |
| Resources | host user limits | cgroups, rlimits, timeout | runaway builds, fork bombs, disk fill |
| Privilege | user's normal privilege surface | no-new-privs, dropped caps, syscall policy | privilege escalation paths |
Core sandbox primitives
The names in sandbox diagrams are easy to skim past, but each one protects a different axis of authority. Landlock is a Linux security module that lets an unprivileged process restrict its own filesystem access with allowlists: for example, read these system directories and write only this worktree. seccomp is a Linux syscall filter. It can deny or trap kernel calls such as mount, ptrace, bpf, or socket families the workload should not use.
Namespaces give a process a private view of some kernel resources. A PID namespace hides host processes, a mount namespace gives a private filesystem layout, a user namespace maps privilege inside the sandbox without giving host root, and a network namespace gives the workload its own network stack. cgroups are Linux resource controllers: they bound memory, process count, CPU, and sometimes I/O so a build, test, or generated program cannot consume the whole machine.
Egress means outbound communication from the sandbox to something else. In agent security, egress usually means network egress: can the workload contact the internet, package registries, model APIs, internal services, or raw IP addresses? A deny-network sandbox removes the channel. An egress allowlist permits only named destinations. This matters because filesystem isolation does not stop data theft if the process can still send the data somewhere.
Landlock # file tree rules: this process may read/write only these paths seccomp # syscall rules: this process may not ask the kernel for dangerous operations namespace # private view: this process sees its own /proc, mounts, network, or users cgroup # resource budget: this process group gets bounded memory, CPU, PIDs, I/O egress # outbound channel: this process may contact only these destinations
Why "bits present" is not "confinement works"
A subtle and underappreciated failure mode: the kernel can advertise a primitive that does not actually enforce anything in your environment. /sys/kernel/security/lsm can list Landlock, seccomp can be a valid prctl, and unprivileged user namespaces can be compiled in, and yet a confined process can still fail to launch. A common cause is an AppArmor or sysctl policy that restricts unprivileged user namespaces (Ubuntu and some CI runners ship this), so the unshare(CLONE_NEWUSER) that the sandbox relies on returns EPERM at runtime even though every feature bit reads "supported."
The honest response is a functional self-test, not a feature probe. h5i's process tier does exactly this: before it will create a kernel-confined environment, it runs a trivial command (true) inside the full confinement and checks that it actually executes. If the probe fails, env create refuses with a specific message ("the kernel reports Landlock/user-namespace/seccomp support, but a confined command could not execute") and tells you to re-request the weaker, clearly-labeled workspace tier. The principle generalizes: never infer enforcement from capability bits; verify it, and fail closed, refusing to run rather than silently downgrading to a sandbox that is not there. A silent downgrade is worse than no sandbox, because it produces a false security claim you will later rely on.
Isolation versus mediation
There are two broad enforcement styles. Isolation changes what the workload can see: a private filesystem view, a private PID namespace, a private network namespace, or a whole virtual machine. Mediation leaves the world conceptually visible but inserts a decision point: a seccomp-notify supervisor, an HTTP proxy, a broker for secrets, or a file-open policy engine.
Isolation is simple to reason about when it is complete. If there is no network device, the process cannot send packets. Mediation is more flexible when decisions depend on runtime state. A proxy can allow pypi.org but deny paste.example; a secrets broker can release a token only to one command and redact it from logs. Most real sandboxes combine both styles.
| Question | Isolation answer | Mediation answer |
|---|---|---|
Can it read ~/.ssh? | Do not mount or allow that path. | Ask a file broker whether this open is allowed. |
| Can it reach the internet? | Give it no network namespace route. | Route traffic through an allowlist proxy or socket gate. |
| Can it use a secret? | Do not put the secret in the environment. | Release a named secret through a broker and redact logs. |
| Can it fork forever? | Put it in a cgroup with a PID budget. | Supervisor kills the run when budget is exceeded. |
The AI-specific problem
AI agents create a new composition problem. A normal sandbox for untrusted code asks: can this program harm the host? An agent sandbox must also ask: can this program influence the agent into using its tools against the host? That is why repository text matters. A README can tell the model to run a curl command. A test failure can print a "fix" that copies credentials. A dependency script can alter files that the agent later reads as trusted context.
The hard case is the "lethal trifecta": private data, untrusted content, and network egress in the same decision context. If a process can read secrets, read attacker-controlled instructions, and talk to the internet, sandbox policy has already given the attack all three ingredients. Strong designs split stages: fetch untrusted content without secrets, process secrets without network, and publish only sanitized output.
# Stage 1: fetch public/untrusted input, but no secrets are present. fetch_untrusted: network = allow, secrets = none # Stage 2: process private data, but no network exists. process_private: network = deny, secrets = needed # Stage 3: publish a sanitized result to a narrow destination. publish_result: network = allowlist, secrets = publish-token only
What sandboxes defend
A sandbox can defend against accidental writes, opportunistic exfiltration, many prompt-injection payloads, dependency scripts that expect a normal home directory, and a large class of process-level attacks. It can make risky work auditable and reversible. It can let several agents run at once without clobbering each other's checkouts. It can make a raw mistake cheap.
It cannot make arbitrary hostile code safe merely by naming itself a sandbox. Shared-kernel isolation still shares the host kernel. If the workload can reach a vulnerable kernel surface through an allowed syscall, a kernel exploit can become a host escape. Containers, Landlock, seccomp, and namespaces are important, but they are not the same category as a microVM with a separate guest kernel.
There is also a class of leaks that no syscall filter or filesystem allowlist addresses: side channels. A confined process that shares CPU cores with the host can still observe timing, cache, and branch-predictor effects; it can read coarse host signals through whatever /proc and /sys entries remain mounted; an egress allowlist that resolves domains can still be a covert channel through DNS query patterns or request timing. These are not reasons to skip the primitives. They are reasons to be precise about the claim. "The agent cannot open this file" is enforceable and worth enforcing. "The agent cannot exfiltrate one bit by any physical means" is a much stronger statement that shared-kernel, shared-CPU sandboxing does not make.
A simple taxonomy
Sandboxes for agents fall into five families. A worktree sandbox isolates edits but not execution. A process sandbox uses host-kernel primitives such as namespaces, seccomp, Landlock, Seatbelt, or bubblewrap. A container sandbox packages process isolation with an image and runtime. A user-space kernel such as gVisor intercepts much of the Linux ABI before it reaches the host kernel. A microVM such as Firecracker runs the workload behind a guest kernel and hypervisor boundary.
The categories are not a strict ladder for every workload. Worktrees are excellent for merge workflow and terrible as a security boundary. Process sandboxes are fast and local but share the kernel. Containers are ergonomic but frequently misconfigured. gVisor and Kata improve the kernel boundary at operational cost. MicroVMs raise the isolation ceiling but require VM images, KVM, boot plumbing, and careful network and file integration.
The same families show up as the tier ladder in h5i's env (covered in Part 4): workspace is the worktree-only tier, giving isolation for parallel edits with no execution confinement; process is the host-kernel tier, with a Landlock filesystem allowlist, a seccomp deny-list, namespaces, and rlimits, forking a supervisor in a fresh PID namespace; supervised adds seccomp-notify mediation on top; and container runs the workload in rootless Podman, which is the tier where an outbound domain allowlist (net.egress) becomes enforceable through a host-side allowlist proxy. The names are deliberately not interchangeable: each tier states a different ceiling, and a higher tier is a different security claim, not just "more secure."
The audit requirement
AI-agent sandboxing is not only prevention. It is also evidence. Reviewers need to know which command ran, under which policy, with which denied operations, which secrets were released, which hosts were contacted, which files changed, and whether the policy was silently downgraded. A sandbox that blocks actions but leaves no reviewable record is useful for containment but weak for engineering governance.
This is where agent sandboxes differ from many classical sandboxes. The output is not just "program returned 0." The output is a proposed code change. The reviewer needs the diff and the story: why this environment existed, what the agent tried, what evidence it saw, and whether any boundary pressure appeared. That is why later parts of this series treat provenance as part of the sandbox rather than an afterthought.
Checklist for evaluating a sandbox
- State the adversary: accident, prompt injection, hostile code, or hostile tenant.
- List the exact capabilities granted to the workload.
- Identify the enforcement primitive for each capability.
- Ask whether the workload can modify the policy after start.
- Ask whether DNS, raw IP sockets, proxies, and Unix sockets match the network claim.
- Ask whether secrets are absent, brokered, or simply passed as environment variables.
- Check whether resource limits are kernel-enforced or best-effort.
- Check whether denied actions are recorded in a form reviewers can inspect.
- Check whether the tool fails closed when a requested boundary cannot be enforced.
Conclusion
Sandboxing an AI coding agent is not a feature you turn on; it is a claim you make and then have to defend. The claim has four moving parts (the adversary you are containing, the capabilities you grant, the primitive that enforces each one, and the evidence left behind) and every one of them has to be stated, not assumed. The threat model is concrete: untrusted code execution, data exfiltration, privilege escalation, resource exhaustion. The defense is also concrete: namespaces for visibility, seccomp for syscall surface, Landlock for filesystem reach, cgroups and rlimits for resources, egress policy for the network. None of these is a sandbox by itself; composed, with fail-closed defaults and a functional check that the composition actually holds, they become one.
The discipline is mostly in the honesty. Name the ceiling. Refuse to run when the boundary cannot be enforced rather than pretending it was. Do not call a permission prompt a sandbox, and do not call shared-kernel confinement a defense against a kernel exploit. An agent sandbox that does these things will not make hostile code safe, since nothing short of a separate guest kernel comes close to that, but it will make the common cases (accidents and most prompt-injection payloads) contained, auditable, and reversible, which is the actual job. The rest of this series turns these principles into code, compares the tools that implement them, and walks through how h5i wires them together.
FAQ
Is a permission prompt the same as a sandbox? No. A prompt asks the user to approve a tool call before it runs; a sandbox enforces a rule regardless of what the user, model, dependency, or shell decides. Prompts are a useful workflow control and depend on a human catching the right moment. They are not a containment boundary, and they do nothing once a command has been approved or once untrusted text has steered the agent.
What does each Linux primitive actually contain? Each contains one axis of authority. Landlock restricts filesystem reach (which paths a process may read or write). seccomp-bpf restricts the syscall surface (which kernel calls are even reachable). Namespaces restrict visibility (private PID, mount, network, user views). cgroups and rlimits bound resources (memory, process count, CPU, file size, wall time). Network namespaces plus an egress policy bound where packets can go. Only their composition is a meaningful sandbox; any single one leaves the other axes wide open.
Why isn't a container enough for untrusted code? A standard container shares the host kernel. If the workload can reach a vulnerable kernel surface through an allowed syscall, a kernel exploit can become a host escape. Containers, Landlock, seccomp, and namespaces are the right tools for containing accidents and most prompt-injection payloads, but they are a different category from a microVM (such as Firecracker) or a user-space kernel (such as gVisor) that interposes a separate guest kernel between the workload and the host.
What does "fail closed" mean for a sandbox? It means that when a requested boundary cannot be enforced, the tool refuses to run instead of silently continuing without it. The dangerous alternative is a silent downgrade: the kernel reports that Landlock, seccomp, and user namespaces are present, but a confined command cannot actually execute (for example, AppArmor restricts unprivileged user namespaces), and the tool runs anyway. That produces a false security claim you will later rely on, which is worse than no sandbox at all. The right pattern is a functional self-test that verifies confinement actually holds before granting it.
Further reading
Official references for this series: Anthropic's Claude Code sandboxing announcement, the sandbox-runtime repository, Dagger's container-use, OpenSandbox, E2B docs, gVisor docs, Kata Containers, and Firecracker.
Sandboxing is an engineering claim
Try h5i on your next AI-assisted branch: create a sandboxed workspace, capture the run, and post a review-ready PR brief.
Star on GitHub Back to blog