# 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](/docs/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](/compare/pyodide-vs-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](/docs/security)): - **Network:** off, or narrowed to named hosts; private addresses are always refused ([turn off sandbox internet](/how-to/turn-off-sandbox-internet)). - **Time:** commands time out, and the sandbox stops when its lease ends. - **Money:** `maxCostMicros` refuses a create whose first lease would cost more. ```ts import { 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); ``` ```python from withruntime import Sandbox code = "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: ```bash no-run npx withruntime sandbox run --trial -- python3 -c 'import subprocess; print(subprocess.run(["id"]))' ``` More: [microVM vs container](/compare/microvm-vs-container), [run untrusted LLM code](/use-cases/run-untrusted-llm-code), [what is a microVM?](/glossary/microvm), [unikernel vs microVM](/compare/unikernel-vs-microvm). ## Sources Checked 25 September 2026. - [WebAssembly security](https://webassembly.org/docs/security/), [WASI](https://wasi.dev/) - [Wasmtime](https://wasmtime.dev/), [Wasmtime security](https://docs.wasmtime.dev/security.html), [Wasmtime security advisories](https://github.com/bytecodealliance/wasmtime/security/advisories) - [Pyodide WebAssembly constraints](https://pyodide.org/en/stable/usage/wasm-constraints.html), [Pyodide FAQ](https://pyodide.org/en/stable/usage/faq.html) - [Firecracker](https://firecracker-microvm.github.io/), [specification](https://github.com/firecracker-microvm/firecracker/blob/main/SPECIFICATION.md), [design](https://github.com/firecracker-microvm/firecracker/blob/main/docs/design.md) Facts on this page were checked on 25 September 2026.