WebAssembly vs microVM: is Wasm safe enough for LLM code?
WebAssembly isolates one compiled module inside a runtime; a microVM isolates a whole Linux machine behind hardware virtualization.
Runtime gives model-written code a whole Firecracker microVM, so it runs as
written: Python, Node.js, shell, pip install, subprocesses. A new sandbox
ran its first Python command 351 ms after the create request at the median,
over the public internet, on 24 September 2026 (speed).
How WebAssembly isolates code
WebAssembly is a bytecode format run by a runtime such as Wasmtime. The specification's security page says "each WebAssembly module executes within a sandboxed environment separated from the host runtime using fault isolation techniques." A module has its own linear memory, and accesses to it are bounds-checked. Calls go only to declared functions, and indirect calls are type-checked. A module can reach the outside world only through the functions its host chose to import.
WASI extends that to system resources. In its words, a module "starts with no ambient authority and can only do what the host explicitly grants." A host that grants no directory gives the module no files; one that grants no sockets gives it no network.
Wasmtime adds defence in depth on top: a 2 GB guard region before each linear memory, memory zeroed after an instance ends where it can be, Spectre mitigations on bounds checks, and around-the-clock fuzzing through Google's OSS-Fuzz.
How a microVM isolates code
A microVM runs a guest Linux kernel under a hypervisor. Firecracker, which Runtime uses, asks the host's KVM for a virtual machine and gives it what its site counts as "only 5 emulated devices". The code inside can do anything Linux allows, root included, because the boundary is the virtual machine itself. Firecracker's design document starts from the worst case: "all vCPU threads are considered to be running malicious code as soon as they have been started."
Side by side
| WebAssembly (Wasmtime with WASI) | MicroVM (Firecracker) | |
|---|---|---|
| Unit of isolation | One module in a runtime, often inside your own process | A virtual machine with its own Linux kernel |
| Boundary | Software fault isolation, validated by the runtime | Hardware virtualization through KVM |
| What the code can call | Only functions the host imports; WASI grants per capability | Any Linux system call, answered by the guest kernel |
| What reaches the host | The runtime's code and every host function you expose | 5 emulated devices, in the project's count |
| Languages | Anything compiled to Wasm; Python needs a Wasm build | Any Linux program, unchanged |
| Packages | Must be built for Wasm; native extensions need cross-compile | apt, pip, npm as on any Linux machine |
| Processes and threads | Pyodide, CPython's Wasm port, cannot start either | Normal Linux processes and threads |
| Start time | No single figure published by Wasmtime | Up to 125 ms to guest init, in Firecracker's specification |
| Gaps the spec names | Code reuse, TOCTOU races, timing side channels | The guest kernel is the code's own; the host's is not shared |
Is WebAssembly safe enough for LLM code?
For a small, pure function compiled to Wasm and given no imports, the sandbox is strong and cheap. The question is what the code needs, because each import is a hole you cut on purpose.
Model-written code rarely stays pure. It opens files, installs packages, calls APIs and starts processes. Each of those becomes a host function that your Wasm host implements and has to get exactly right. The specification says "other classes of bugs are not obviated by the semantics of WebAssembly": code reuse attacks against indirect calls, time-of-check to time-of-use races, and side channels such as timing attacks.
The host layer is where real bugs land. On 20 August 2026 Wasmtime published a High-severity advisory, GHSA-vqjp-4c8c-hfgg, "Filesystem sandbox escape when paths or symlinks contain trailing slashes", in its WASI implementation. That was a flaw in the capability layer, not in Wasm itself, which is the point: the more of the operating system you expose, the more of an operating system you are writing.
A microVM moves that work into a real Linux kernel that the code owns. If the code breaks the guest kernel, it has broken only its own disposable machine.
Can WebAssembly run Python or pip install?
Only in a Wasm build. Pyodide ports CPython to WebAssembly; its package
installer takes pure-Python wheels and packages cross-compiled for Pyodide,
and threading, multiprocessing and subprocess raise RuntimeError.
Pyodide vs a server sandbox covers the
limits in full. A microVM runs the Python on the machine, so any wheel on PyPI
installs.
When each fits
- Pick WebAssembly for plugins and user-defined functions inside your own service, edge handlers, and code you compile yourself with a narrow, known interface, where the host decides every capability.
- Pick a microVM for code an agent or model wrote in an ordinary language, which expects a shell, a package manager, a filesystem, subprocesses and the network, and which you cannot review before it runs.
Why Runtime uses a microVM
Runtime gives each sandbox its own Firecracker microVM with Ubuntu 24.04, Python 3.12, Node.js 24, Bun, git and gcc. The controls a Wasm host would implement live on the Runtime host, outside the guest, so root inside cannot change them (security):
- Network: off, or narrowed to named hosts; private addresses are always refused (turn off sandbox internet).
- Time: commands time out, and the sandbox stops when its lease ends.
- Money:
maxCostMicrosrefuses a create whose first lease would cost more.
TypeScriptimport { Sandbox } from "withruntime";const code = "import subprocess; print(subprocess.run(['uname', '-r'], capture_output=True, text=True).stdout)";await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 300, onLeaseEnd: "stop",});await sbx.files.write("/workspace/answer.py", code);const run = await sbx.exec(["python3", "answer.py"], { timeoutMs: 30_000 });console.log(run.exitCode, run.stdout);Pythonfrom withruntime import Sandboxcode = "import subprocess; print(subprocess.run(['uname', '-r'], capture_output=True, text=True).stdout)"with Sandbox.create( network={"internet": False}, timeout_seconds=300, on_lease_end="stop",) as sbx: sbx.files.write("/workspace/answer.py", code) run = sbx.exec(["python3", "answer.py"], timeout_ms=30_000) print(run.exit_code, run.stdout)The model's subprocess call works, because the sandbox is a real Linux
machine; the network stays off whatever the code tries.
Try it on the free trial, 50 sandbox hours with no card:
Terminalnpx withruntime sandbox run --trial -- python3 -c 'import subprocess; print(subprocess.run(["id"]))'More: microVM vs container, run untrusted LLM code, what is a microVM?, unikernel vs microVM.
Sources
Checked 25 September 2026.
- WebAssembly security, WASI
- Wasmtime, Wasmtime security, Wasmtime security advisories
- Pyodide WebAssembly constraints, Pyodide FAQ
- Firecracker, specification, design
Facts on this page were checked on 25 September 2026.