No. 22 · JUN 2026 · 7 Min Read
Where User Code Runs
Abstract
MicroVMs or V8 isolates? The boundary you pick for untrusted user code sets your blast radius, your latency, and your cost. A field guide to both.
In Your Users Already Run AI-Generated Code I made the case: your non-technical teams want to build against your product, the right answer is a box of safe primitives, and the code they write is a strange hybrid. First-party in that it serves one tenant and is never published. Third-party in that you must treat every line of it as untrusted.
That last word is where the architecture starts. Untrusted code cannot run in your process. It cannot run with your credentials. In a multi-tenant system it cannot run anywhere it might see another tenant’s data. The entire problem reduces to one question with real engineering behind it: what is the wall, and how high is it.
The cheap non-answer is to run it in your own process anyway, eval inside a worker you already operate, and trust the code to behave. Node’s own documentation says the quiet part out loud: the vm module is not a security mechanism. A sandbox built from a language runtime still shares your process, your credentials, and your address space with code you just agreed to treat as hostile. The wall has to sit beneath the language runtime, not inside it.
There are two serious answers, plus a middle one. Hardware virtualization. Language-level isolation. And a hardened-container layer that splits the difference. They are not interchangeable, and the cost of picking wrong shows up during an incident.
The Hardware Wall: MicroVMs
A microVM is a real virtual machine with a real guest kernel, stripped of everything a server VM carries that a sandbox does not need. No BIOS, no PCI bus emulation, no device zoo. Just enough virtual hardware to boot a Linux kernel and run a process, behind the same CPU-enforced boundary that separates any two VMs on a host.
AWS open-sourced the canonical implementation, Firecracker, in 2018. It’s a virtual machine monitor built on Linux KVM, written in Rust, and it’s the substrate under AWS Lambda and Fargate. The numbers are the reason it matters for this problem. A microVM boots in about 125 milliseconds. A single host can launch around 150 of them per second. Each one adds less than five megabytes of memory overhead, so you pack thousands onto a box. In production it runs behind the jailer, a separate process that sets up cgroups and Linux namespaces and drops privileges before the VMM ever starts. Defense in depth around a boundary that is already hardware-enforced.
The reason to reach for this is the strength of the wall. A microVM gives the guest its own kernel. An attacker who fully compromises the code inside, root in the guest, a kernel exploit, the works, is still standing inside a VM, facing the hypervisor, which is the most battle-tested isolation boundary the industry has short of separate physical machines. For code you genuinely do not trust, running arbitrary languages, writing to a real filesystem, spawning processes, this is the boundary to use.
AWS now sells this shape directly, as of June 22, 2026. Lambda MicroVMs launched that day. You hand it a Dockerfile and a code artifact, it builds an image and snapshots an initialized Firecracker VM, and run-microvm returns an isolated instance with its own kernel and a dedicated HTTPS endpoint. One image, many instances, each configured at launch and authenticated at the door. It suspends when idle and resumes from the snapshot near-instant, out to eight hours of runtime. AWS is explicit that it is purpose-built for multi-tenant applications: AI coding assistants, interactive code environments, code that runs user-supplied scripts. The raw primitive this series keeps describing, rented by the second.
The managed-agent variant predates it. Amazon Bedrock AgentCore went GA in October 2025 as a runtime for agents and their tools, and its documentation is blunt about the model: each session runs in an isolated microVM with filesystem and shell access. Its Code Interpreter runs model-generated Python and JavaScript with, in AWS’s words, complete isolation from other workloads and the broader infrastructure. One session, one sandbox, torn down at the end. Where MicroVMs hand you the bare machine, AgentCore wraps the same isolation in an agent runtime.
You can also assemble it yourself. E2B runs Firecracker-backed sandboxes aimed squarely at agent code. Fly.io Machines hand you raw Firecracker microVMs as a low-level brick to build your own layer on. Snapshotting is the trick that makes any of these feel instant: a microVM restored from a memory snapshot resumes in tens of milliseconds instead of paying a cold boot, which is how a sandbox that “starts in 150ms” can answer in a fraction of that under load.
The Software Wall: V8 Isolates
The other model throws out the VM entirely. A V8 isolate is a sandbox inside a single operating-system process. Many isolates share one process, each with its own heap and garbage collector, separated by the V8 engine’s memory boundaries rather than the CPU’s.
Cloudflare Workers are the reference implementation. Because there is no VM and no container to boot, there is effectively no cold start. An isolate spins up in around five milliseconds and uses an order of magnitude less memory than a process would. You can run a tenant’s code, tear it down, and run the next tenant’s on the same hardware with almost no per-invocation tax. The constraints are the flip side of that density: a hard memory ceiling, 128 megabytes per isolate, and a runtime that gives you JavaScript and WebAssembly, not an arbitrary Linux process with a shell and a filesystem.
The wall here is a different kind. Isolate isolation is software isolation. It rests on V8 doing its job, which means the entire class of CPU side-channel and engine-escape concerns is in scope in a way it simply is not for a hardware VM. Cloudflare knows this and does not pretend otherwise. Their security model is explicit that they trade full OS-level isolation for speed and then spend enormous effort hardening it: Linux namespaces and a seccomp filter that blocks every filesystem and network syscall, applied before any isolate loads, so a hypothetical escape lands in a process that can do almost nothing. It is a serious, well-defended boundary. It is not a hypervisor.
For multi-tenant user code specifically, Cloudflare has built the surface out. Workers for Platforms uses dynamic dispatch to route requests across many tenant-uploaded Workers, each isolated, which is the multi-tenant user-code pattern as a product. And Code Mode, introduced in late 2025, leans into the model I keep coming back to: instead of an agent making a long sequence of tool calls, it writes code that calls typed APIs, and that code runs in an isolate. Cloudflare reports it cuts token usage dramatically because the model writes one program instead of narrating twenty tool invocations. Their newer Dynamic Workers extend this by loading model-generated TypeScript into an isolate that calls MCP tools as typed bindings. Heavier full-Linux workloads moved to Cloudflare Containers and the Sandbox SDK, which went GA in 2026 when the isolate model was the wrong fit.
The Middle: Hardened Containers
Between the two sits a layer worth knowing. A plain container is not an isolation boundary for hostile code; it shares the host kernel, and a kernel bug is a tenant escape. Hardened-container runtimes fix this by putting a barrier in front of the kernel. gVisor, Google’s approach, runs a user-space kernel that intercepts the guest’s syscalls and services most of them itself, so the real host kernel is rarely touched directly. Modal uses this style of isolation to run code with sub-second starts.
It’s a reasonable middle: stronger than a bare container, lighter than a full VM, with a larger trusted computing base than a microVM because that user-space kernel is now code in your attack surface. I treat it as the choice when you need real Linux semantics and broad language support but the workload is more “our customers” than “the open internet,” and the operational simplicity is worth a slightly softer wall.
Choosing
The decision is a match between the wall, the threat, and the workload. Four questions settle it.
| Question | Pushes you toward |
|---|---|
| What language do users write? | Arbitrary (Python, binaries, a shell) → microVM. JS/TS/Wasm only → isolate. |
| How hostile is the code? | Genuinely untrusted, root-in-guest must be contained → microVM. Your-tenants, defense-in-depth acceptable → isolate or gVisor. |
| What’s the latency budget? | Sub-10ms per call, massive fan-out → isolate. Hundreds of ms acceptable, or snapshot-restore in play → microVM. |
| Does it need a real filesystem and processes? | Yes → microVM. No, pure compute on passed-in data → isolate. |
If your users need to run real programs in real languages against a real filesystem, and you cannot afford to be wrong about isolation, you want a microVM, and AgentCore or a Firecracker-based layer gets you there. If your users are composing logic in JavaScript or Wasm over data you hand them, at scale, on a tight latency budget, isolates are dramatically cheaper and faster and the security model is good enough when you respect its limits.
For primitives your users assemble against their own data, I default to the microVM. A Sandbox for User Code builds on exactly that: a sandbox for execution, an ABAC policy deciding what every tenant’s code may touch, and a filesystem as the first thing the code runs against.