Sandboxing code an agent wrote
Model-generated code has to run somewhere, and the choice of isolation sets your blast radius. This page compares containers, microVMs, WebAssembly, and hosted sandboxes on startup latency, filesystem and network scope, credential exposure, and operational cost, then states which one each workload profile actually needs.
The decision, stated plainly
If an agent writes code and something executes it, you have chosen an isolation model. Most teams choose by default, and the default is “the same process, the same machine, the same credentials as the agent itself” — the one option with no boundary at all.
That default is survivable while an agent is a toy you run by hand. It stops being survivable the first time the agent reads something you did not write: a dependency’s README, an issue description, a scraped page, a colleague’s branch. At that point the code being executed is downstream of text an outsider controls, and the question stops being hypothetical.
This page covers four options — containers, microVMs, WebAssembly, hosted sandboxes — on the four criteria that actually differ between them, and ends with which profile takes which.
What you are isolating against
The three risks push toward different answers, which is why “is it sandboxed?” is not a useful question on its own.
Accidental damage. The common case, and the least interesting. Generated code deletes the wrong directory, fills a disk, or loops on a network call. Any real boundary handles this, including the cheapest one.
Credential reach. The agent’s environment holds tokens — a cloud key, a package registry credential, a database URL. Code executing in that environment inherits every one of them. This is a scoping problem wearing a sandboxing costume, and it is the risk most often left unaddressed, because a sandbox feels like it has dealt with it.
Deliberate escape. Relevant when the code is downstream of untrusted input. Prompt injection is what makes this ordinary rather than exotic: an attacker who can write text your agent will read is an attacker who can, in effect, submit code for execution. Simon Willison’s writing on prompt injection remains the clearest survey of why this class of attack has no clean fix at the model layer, which is exactly why it has to be handled at the execution layer.
The four options
Containers
A shared kernel with namespaces and cgroups drawing the boundary. Startup is fast from a warm pool, tooling is universal, and every engineer on the team already understands the failure modes.
The boundary is real but it is one kernel deep. Container escapes are rare and they are not theoretical; the Linux kernel’s own documentation on seccomp exists because syscall surface is the thing you are trying to reduce. Hardened runtimes (gVisor, Kata) close most of the gap at some cost in syscall performance.
Good against accidents. Adequate against a curious model. Not what you want between an attacker and your kernel.
MicroVMs
A separate kernel per workload, with a hardware virtualisation boundary, at a startup cost in the tens of milliseconds rather than single digits. This is what most hosted code-execution products run underneath — Firecracker was built for exactly this shape of workload, and its design notes are worth reading before choosing anything else.
The trade is straightforward: a genuine kernel boundary for a slower start and more machinery to operate.
WebAssembly
The tightest boundary and the smallest startup cost. A Wasm module has no ambient authority at all: no filesystem, no sockets, no clock, unless something is explicitly granted through WASI. Deny-by-default is the actual default rather than a configuration you remember to apply.
You pay in compatibility. The moment generated code wants a native dependency, a subprocess, or a library that assumes a real POSIX environment, you are outside what Wasm does comfortably. Excellent for computation over data you hand it; awkward for “run this Python script that pip-installs three things”.
Hosted sandboxes
Somebody else’s microVMs behind an API. You trade unit cost for operating none of it, and you inherit their isolation properties, their uptime, and their data handling.
Below a threshold of volume this is straightforwardly the right answer, because the engineering time to build and maintain the alternative costs more than the bill. Above it, the arithmetic inverts. The threshold is specific to your volume, and it is worth actually computing rather than assuming.
Startup cost is the axis people underestimate
Isolation strength and startup latency trade against each other, and startup latency is what determines whether your agent design is even viable. An agent that executes forty short snippets in a run behaves completely differently at 3ms per execution than at 900ms.
The figures above are order-of-magnitude, for shape rather than for citation — they are what these technologies are built to deliver, not measurements of your workload. The point is the ratio, which holds across implementations: roughly two orders of magnitude between the lightest and heaviest option. If your agent’s access pattern is many small executions, that ratio decides your architecture. If it is one long execution per run, it barely matters and you should pick on isolation strength alone.
Measure your own before committing. The number that matters is not cold start in isolation but cold start times executions per run, and that product is a property of your agent design, not of the sandbox.
Scope beats isolation, and it is cheaper
Whichever boundary you pick, the credential question is separate and more important.
Generated code should execute holding a token that can do exactly the one thing the task requires, minted for that task, expiring when it ends. A perfect sandbox holding a production key is worse than a weak sandbox holding nothing, because the perfect sandbox buys confidence it has not earned.
Egress deserves identical treatment. Exfiltration needs no filesystem access and no persistence — a single outbound request is sufficient. Default-deny with an allowlist of hosts the task genuinely needs is more protective, and far cheaper to implement, than upgrading your isolation tier. The OWASP guidance on LLM applications puts excessive agency and insecure output handling near the top of its list for this reason.
If you do one thing from this page, make it this one. It is a day of work and it removes more risk than a quarter spent on isolation.
Which to pick when
Internal code, inputs you control, accident-shaped risk. Containers from a warm pool. Spend the effort you saved on credential scope and egress rules, which is where your actual exposure is.
Anything downstream of untrusted text — public repositories, issue trackers, scraped content, user-submitted anything. A real kernel boundary: microVMs, or a hosted sandbox built on them. Container isolation is not the right tool for an adversary.
Pure computation, no native dependencies, latency matters. WebAssembly. Deny-by-default and single-digit startup are exactly what a high-frequency execution loop wants, and the compatibility ceiling is not binding if the code is arithmetic over data you supply.
Low volume, small team, no platform engineer to spare. A hosted sandbox, and revisit when the monthly bill approaches the cost of the engineer-weeks required to replace it. That crossover is the only number worth tracking here.
Sources
- Simon Willison's writing on prompt injection simonwillison.net
- Linux kernel's own documentation on seccomp docs.kernel.org
- Firecracker firecracker-microvm.github.io
- WASI wasi.dev
- OWASP guidance on LLM applications owasp.org
See also
-
Billing shape, duration ceilings, warm state, cold starts and concurrency limits compared for agent runtimes — with a per-condition recommendation.
-
A step-by-step guide to building a CI suite for AI systems: property assertions, pass-rate thresholds, and setting a tolerance your team will not ignore.
-
Cloudinary's unauthenticated agent account-creation endpoint: what it returns, why email verification gates it, and when to use it.
-
What a vendor skill pack is, how Cloudinary's installs and lets a team select skills, and why vendor-side versioning changes assistant behaviour without review.