AI Agent Sandboxing: How to Let a Model Run Code Without Losing the Building
Once an agent can execute code, the sandbox is the security control, and the three sandboxes most organizations are running are the three that do not hold: a Python interpreter with the dangerous builtins removed, a container sharing the host kernel, and a long-lived virtual machine reused across every task. The architectural conclusion of prompt injection is that injection cannot be prevented, only contained, and containment has a specific technical meaning that most agent deployments have not met. An agent that writes and runs code is an arbitrary code execution path that the organization deliberately built, pointed at its own infrastructure, and fed untrusted input. Whether that is reckless or routine depends entirely on what sits between the generated code and everything else. This piece covers the isolation ladder and what each rung genuinely stops, the four dimensions of sandbox design that get neglected because everyone is thinking about CPU, how to scope credentials so an escape is worth less, and the failure modes that survive even a perfect sandbox.

Key Takeaways
- Isolation is a ladder, not a switch. Interpreter-level guards stop almost nothing, shared-kernel containers are a real but shallow boundary with a documented escape history, user-space kernels such as gVisor and microVMs such as Firecracker or Kata are the boundary that holds, and a separate cloud account with its own network is what holds when the others are misconfigured.
- Network egress policy is the single highest-value control and the one most commonly left wide open. A sandbox with unrestricted outbound access is a data exfiltration channel with extra steps, regardless of how strong the kernel boundary is.
- The right lifetime is one sandbox per task, destroyed afterward. Reused sandboxes accumulate state, credentials, and installed packages from previous tasks, which converts a single compromised task into persistent access.
- Credentials decide what an escape is worth. Short-lived, task-scoped tokens minted per run bound the damage far more reliably than any isolation technology, because the attacker who breaks out inherits only what the token carried.
- No sandbox stops exfiltration through an allowed API, actions taken within granted permissions, or a malicious package installed at runtime. These are policy and provenance problems, and treating the sandbox as their answer is the most common design error.

What a Sandbox Is Doing in an Agent System
An agent sandbox is an execution environment that runs model-generated code and tool calls with a hard boundary between that code and every resource it was not explicitly granted. The distinction from ordinary application sandboxing is the threat model. In conventional systems the code is trusted and the input is not. In agent systems the code itself is generated at runtime from input that may be adversarial, which means every property of the workload is attacker-influenced: what runs, what it reads, what it installs, and where it connects.
That inversion matters because it invalidates a common assumption. Teams reason about agent sandboxes the way they reason about running their own CI jobs, where the concern is a buggy build script rather than a hostile one. The correct reference model is a public multi-tenant code-execution service, where any user may be an attacker and the platform assumes the workload is trying to escape. The organizations that run these services at scale did not converge on containers.
There is a second reason the boundary carries so much weight. Detection controls sit in front of the model and can be bypassed by a sufficiently novel input, which is why the layered controls covered in AI guardrails for production LLM systems reduce attack surface without ever becoming a boundary. The sandbox is different in kind: it does not attempt to judge whether the code is malicious, it constrains what any code can reach. Controls that reason about intent degrade under adversarial pressure. Controls that constrain capability do not.

The Isolation Ladder
Each rung is strictly stronger than the one below and strictly more expensive. The engineering decision is which rung the workload actually requires, and the common error is stopping two rungs early because the first one was easy to deploy.
| Rung | What it genuinely stops | What still gets through | Cost | Typical fit |
|---|---|---|---|---|
| Interpreter guards (restricted builtins, AST allowlists) | Accidental misuse and casual mistakes | Almost every deliberate attempt; introspection escapes are well documented | Negligible | Demos and trusted internal prototypes only |
| Language runtime sandboxes (WebAssembly, isolates) | Memory-unsafe escapes, host filesystem access by default | Anything the host explicitly imports into the runtime; not a full POSIX environment | Low | Deterministic pure computation, plugins, untrusted formulas |
| Containers on a shared kernel (namespaces, cgroups, seccomp) | Filesystem and process visibility, resource exhaustion, unprivileged escalation | Kernel exploits, and misconfiguration such as privileged mode or a mounted Docker socket | Low | Trusted first-party workloads, not hostile ones |
| User-space kernel (gVisor) | Direct host syscall access; the syscall surface is served by a user-space kernel instead of the host | Escapes in the sandbox kernel itself, which is a much smaller target | Moderate, with syscall-heavy workload overhead | Multi-tenant agent code execution at scale |
| MicroVM or lightweight VM (Firecracker, Kata Containers) | Kernel-level escape, because the guest kernel is separate from the host kernel | Hypervisor vulnerabilities and hardware side channels, both rare and costly to exploit | Moderate, with cold start latency measured in milliseconds | The default for untrusted code in production |
| Separate account, project, and network | The blast radius of any of the above failing, plus misconfiguration in the layers themselves | Nothing inside that account; cross-account trust relationships if granted | Organizational overhead, little runtime cost | Mandatory for agents touching production adjacent systems |
Three observations are worth drawing out.
Interpreter guards are not a boundary
Attempting to make a language interpreter safe by removing dangerous functions is a strategy with a long record of failure. Python's object model in particular allows an attacker to walk from any object to its class, to the base object type, to the full list of subclasses, and from there back to the functionality that was supposedly removed. Blocking each discovered route produces a list that never terminates. The correct framing is that the interpreter was designed to give code access to the machine, and a filter added afterward cannot revoke that design.
Containers are a real boundary with a documented history
Containers are not security theatre. Namespaces, cgroups, and a well-built seccomp profile genuinely restrict what a process sees and does, and for first-party workloads that boundary is appropriate. The problem is the threat model, not the technology. Container isolation depends on the host kernel correctly enforcing separation across a very large syscall surface, and container escape vulnerabilities in that surface and its tooling are recurring rather than hypothetical. The runc escapes tracked as CVE-2019-5736 and the 2024 Leaky Vessels set (CVE-2024-21626 among them) are the well-known examples, and the pattern they establish matters more than any individual entry: the shared kernel is one vulnerability class away from being no boundary at all.
Most container escapes in practice are not even kernel exploits. They are configuration: a privileged container, a mounted host path, a Docker socket exposed so the agent can build images, a service account token available at the default path. An agent that can read files and make network calls will find these, not because it is adversarial but because it is thorough.
MicroVMs made the strong answer affordable
The historical objection to virtual machines was weight, and that objection is largely obsolete. Firecracker, the virtual machine monitor AWS built for Lambda and Fargate, boots a minimal microVM in well under a second with a memory overhead of a few megabytes per instance, precisely because it strips the device model to a handful of virtio devices. Kata Containers wraps the same idea in a container runtime interface so that existing orchestration keeps working. gVisor takes a different route, intercepting syscalls and serving them from a user-space kernel called the Sentry, which shrinks the host attack surface without a full VM.
The practical guidance is straightforward. If the code being run is model-generated and the input is not fully trusted, a shared kernel is the wrong boundary, and the cost of moving up a rung is now low enough that the tradeoff rarely favors staying.
The Four Dimensions Everyone Forgets
Sandbox conversations default to CPU and memory limits, which are the least important of the constraints. The four below decide outcomes.
Network egress is the control that matters most
Outbound network policy is the highest-value single control in an agent sandbox, and it is the one most often left at the default of unrestricted access. The reason is structural. Nearly every damaging agent scenario ends with data leaving, and every egress path is a candidate exit. A perfect kernel boundary with open outbound access still permits an injected agent to read what it was legitimately given and post it to an attacker-controlled endpoint. The isolation held; the data left anyway.
Default deny with an explicit allowlist is the target posture. The complication is that useful agents need package registries, documentation, and APIs, so a blanket block breaks the product. The workable answer is tiering by task class.
| Agent task class | Egress posture | What it permits | Residual risk to accept |
|---|---|---|---|
| Pure computation on supplied data | Deny all | Nothing outbound; dependencies preinstalled in the image | Near zero; capability is limited to what the image contains |
| Data analysis on internal data | Deny all, with results returned through the orchestrator | No direct outbound; the result path is a structured channel | Exfiltration only through the result itself, which is reviewable |
| Software development tasks | Allowlist package registries and internal source control through a proxy | Dependency installation and repository access | Malicious package content; requires provenance controls, not network controls |
| Research and browsing | Proxy all traffic, log every request, block internal address ranges | General web reads | This is the widest posture; assume anything the agent reads may be exfiltrated by an attacker who plants content |
| Production API interaction | Allowlist exact hostnames, per-task credentials, rate limited | Only the named services | Exfiltration through an allowed API, which no network control addresses |
Two implementation details carry disproportionate weight. Block the cloud instance metadata endpoint explicitly, because it is the fastest path from code execution to cloud credentials. And block private address ranges, because an agent that can reach internal services from inside the network has converted a sandbox into a pivot point. Both are covered by the segmentation principles in zero trust architecture, applied to a workload that happens to be generating its own code.
Filesystem and secrets
The sandbox filesystem should start from an immutable base image with a writable scratch directory that does not survive the task. Secrets should never be mounted as files or injected as environment variables, because both are trivially readable by the code and both persist into logs, crash dumps, and any artifact the agent produces. Where the agent needs an external credential, the orchestrator outside the sandbox should hold it and mediate the call, so the sandbox holds a reference rather than the secret itself.
Time and resource limits
Wall-clock timeouts serve a security purpose beyond cost control. An agent process that has been running for an hour is either stuck or doing something nobody asked for, and a hard kill turns a novel condition into a bounded one. Set CPU, memory, process count, and file descriptor limits as well, mostly to prevent one task from degrading everything else on the host.
Lifetime is the dimension with the clearest answer
One sandbox per task, destroyed when the task ends. Reuse is where agent sandboxing quietly fails. A reused environment accumulates installed packages, cached credentials, scratch files, and shell history from prior tasks, which means a single compromised task becomes a foothold that observes every subsequent one. Pooling warm sandboxes to hide cold-start latency is legitimate, but the pooled unit must be reset to the base image between tasks, not merely cleaned.
Credentials Decide What an Escape Is Worth
The most reliable way to make a sandbox escape unprofitable is to ensure the escaping process holds almost nothing. This is a credential design question rather than an isolation question, and it is where the highest return on effort sits.
The failure pattern is the ambient service account: one identity with the union of every permission any agent task might ever need, available to every run. It is convenient, it is what most teams start with, and it means every task carries the authority of the most privileged task. The alternative is a token minted per run, scoped to the specific resources that run requires, expiring with the task, and carrying an identity that is attributable in logs. The identity model this depends on is developed in AI agent identity and access management, and it is the layer that converts an escape from an incident into a curiosity.
Three additional practices belong alongside it:
Capability references instead of raw credentials. The sandbox receives a handle that the orchestrator can redeem, not the key itself. Code inside the sandbox can use the capability but cannot copy it somewhere useful.
Human approval on irreversible actions. Some actions cannot be undone: money moving, data deleted, production configuration changed, an external message sent. The approval gate belongs at that line, enforced outside the model in code that cannot be argued with. Latency here is the point.
Per-task attribution. Every credential should identify the task that used it, so that incident review can answer which run did what without inference.
Managed or Self-Hosted
Hosted code-execution services now exist from several vendors, providing a sandboxed interpreter behind an API with session lifetimes and network policy as configuration. The tradeoff is the usual one and turns on three variables.
Managed services win on time to production and on the operational burden of keeping isolation patched, which is real and continuous. They cost more per unit of compute, they place the workload and any data it touches in a third-party environment that may not satisfy residency or regulatory constraints, and they constrain the runtime to what the vendor supports.
Self-hosted microVMs win on data control, cost at sustained volume, and configurability, at the price of owning the hardest part. Nested virtualization support, kernel patching, pool warming, and network policy enforcement are all now the organization's problem.
Cold start is the variable most teams underestimate. A microVM boots in milliseconds, but a usable agent sandbox is the microVM plus the filesystem, the runtime, the preinstalled dependencies, and any working data, which realistically lands between one and several seconds. Pre-warmed pools solve the latency and reintroduce the reuse question, which is why the reset-to-base-image discipline matters more once warming is in play.
Observability Is Not Optional Here
Every command executed, every tool call made, every file written, and every outbound connection attempted should be recorded outside the sandbox, in a store the sandbox cannot write to. The reason is specific to agents: after an incident, nobody can reconstruct what happened from the model's own account of it. The conversation log shows what the model said it was doing. Only the execution log shows what ran.
Blocked egress attempts deserve particular attention, because they are the highest-signal event an agent system produces. A sandbox attempting to reach an address outside its allowlist is either a broken task or an attack in progress, and either warrants a look. The telemetry design that makes this tractable across many concurrent agent runs is covered in agent observability in production; the sandbox-specific requirement is simply that the record be complete and external.
What Sandboxing Does Not Stop
Honest scoping is what makes the control useful, and three failure modes survive an otherwise perfect sandbox.
Exfiltration through an allowed API. If the agent is permitted to call a service, it can encode data into what it sends. The isolation boundary is intact and the data still leaves. This is a policy and monitoring problem, addressed by limiting what data enters the sandbox at all, not by strengthening the boundary.
Actions within granted permissions. An injected agent that deletes records it was authorized to delete has committed no escape. Every control here operated as designed. The answer is the approval gate on irreversible actions, plus the discipline of scoping permissions to the task rather than to the role.
Supply chain risk in runtime installs. An agent that installs packages at runtime executes third-party code inside the sandbox, and a typosquatted or compromised package runs with whatever the sandbox holds. Confinement limits the damage, and it does not prevent the execution. Registry proxies with allowlists, pinned dependency sets, and preinstalled base images are the controls that apply, and they belong to the provenance discipline rather than to isolation.
The sandbox is a containment layer. It is not an intent detector, an authorization system, or a provenance check, and every deployment that assumed otherwise learned it in an incident review.
Frequently Asked Questions
Is a Docker container enough to sandbox an AI agent?
Only when the code being run is trusted. Containers share the host kernel, so their isolation depends on that kernel correctly enforcing separation across a very large syscall surface, and container escape vulnerabilities in that surface and its tooling recur on a regular cadence. For model-generated code driven by input that may be adversarial, the appropriate boundary is a user-space kernel such as gVisor or a microVM such as Firecracker or Kata, both of which are now cheap enough that the tradeoff rarely favors staying on a shared kernel.
What is the single most important agent sandbox setting?
Outbound network policy. Almost every damaging agent scenario ends with data leaving the environment, and unrestricted egress means a perfect kernel boundary still permits exfiltration. Default deny with a per-task-class allowlist, with the cloud metadata endpoint and private address ranges explicitly blocked, prevents more realistic harm than any other single configuration choice.
Should agent sandboxes be reused between tasks?
No. One sandbox per task, destroyed at the end, is the correct default. A reused environment retains installed packages, cached credentials, scratch files, and history from earlier tasks, which turns one compromised task into ongoing access to every task that follows. Warm pools are acceptable for latency as long as each instance is reset to its base image rather than merely cleaned.
What is the difference between gVisor and Firecracker?
They solve the same problem at different layers. gVisor runs a kernel in user space that intercepts and serves the workload's syscalls, so the host kernel sees a small, controlled surface instead of the full one. Firecracker is a virtual machine monitor that boots a genuinely separate guest kernel inside a minimal microVM with a stripped device model. Firecracker provides the stronger boundary because the kernels are separate; gVisor typically imposes less overhead for workloads that are not syscall-heavy.
Does sandboxing solve prompt injection?
No, and expecting it to is the most common error in agent security design. Sandboxing bounds what injected instructions can reach, which is the correct goal, but an injected agent acting within its granted permissions has escaped nothing. Sandboxing is one consequence-limiting control alongside task-scoped credentials, human approval gates on irreversible actions, and egress restriction, and it works only in combination with them.
The Bottom Line
The question worth asking of any agent deployment is not whether it has a sandbox but what the sandbox would cost an attacker who already controls the code inside it. In most current deployments the answer is very little: a shared kernel with a known escape history, open outbound access, an ambient service account with standing permissions, and an environment that persists across tasks. Each of those is a decision that was never consciously made, inherited from a prototype that shipped.
The corrected version is not exotic. Model-generated code runs in a microVM or a user-space kernel, in a separate account with its own network, with egress denied by default and allowlisted by task class, holding a credential that was minted for this run and expires with it, in an environment destroyed when the task ends, with every command and connection attempt recorded somewhere the sandbox cannot reach. That configuration does not prevent an attacker from injecting the agent. It makes the successful injection a logged, bounded, uninteresting event, which is the most any architecture can honestly promise.