On April 9, 2026, the Bytecode Alliance disclosed two Critical-severity sandbox-escape vulnerabilities in Wasmtime, one of the most widely deployed WebAssembly runtimes, on the same day: CVE-2026-34987, letting a malicious guest module read or write host memory through the Winch compiler backend (up to roughly 32KiB before or 4GiB after the start of linear memory), and CVE-2026-34971, a Cranelift miscompilation on aarch64 that let a bounds check and the actual memory load target different addresses, producing an arbitrary read/write primitive into the host process. Both were fixed in Wasmtime 36.0.7, 42.0.2, and 43.0.1. Neither affected the default Cranelift-on-x86_64 configuration most production deployments actually run — which is itself the point. WebAssembly's core design genuinely eliminates entire bug classes: no arbitrary jumps, no ambient syscalls, memory access confined to a bounds-checked linear array. But "wasm is sandboxed" is a claim about a specific runtime's implementation of that design, not a property of the bytecode format itself, and 2026 supplied a clean demonstration of the gap. This post works through what the sandbox actually guarantees, where side channels and host-import misconfiguration still leak data, and why the module supply chain deserves the same scrutiny as any other dependency.
What does WebAssembly's sandbox actually guarantee?
WebAssembly's sandbox guarantees rest on two structural properties enforced at validation and execution time, not on convention. First, linear memory: every wasm instance gets a single contiguous byte array, and every load or store is bounds-checked against its current size before it executes — there is no pointer arithmetic that can produce an address outside that array, and no way to obtain a raw pointer into host memory. Second, control-flow integrity: wasm has no indirect jump-to-address instruction; calls and branches only target function indices declared in the module's own table, which closes off the return-oriented and jump-oriented programming techniques that plague native code with a writable-and-executable stack or heap. On top of that, capability-based host access means a module can only invoke functions explicitly handed to it through its imports object at instantiation — there is no ambient filesystem or network access baked into the instruction set the way a native process inherits OS syscalls by default. The WebAssembly System Interface (WASI) formalizes this further, granting capabilities like preopened directory handles explicitly rather than exposing the ambient OS namespace.
Can WebAssembly still leak data through side channels?
Yes, and this is the attack surface wasm inherits from the hardware underneath it rather than from any flaw in the wasm spec. Academic research titled "Swivel: Hardening WebAssembly against Spectre" (arXiv:2102.12730) demonstrated that Spectre-style speculative-execution attacks can leak data across the wasm-to-host boundary, because a wasm engine's bounds checks are ordinary conditional branches, and a CPU will speculatively execute past them before the check resolves — exactly the pattern Spectre exploits in native code. Separately, Genkin et al.'s "Drive-by Key-Extraction Cache Attacks from Portable Code" (USENIX Security 2018) showed that wasm loaded as an ordinary web page — including from a pop-under ad, with no plugin or native code involved — can build cache-eviction sets and timing primitives precise enough to mount Prime+Probe attacks, recovering key material from co-resident ECDH and ElGamal implementations. A 2024 survey of the field, "WebAssembly and Security: a review" (arXiv:2407.12297), catalogs this and related instruction-timing and cache-based side-channel research across both wasm and JavaScript. Browser vendors ship mitigations — reduced timer precision, site isolation, and speculative-load hardening — but these are defenses layered on top of wasm, not properties of the sandbox itself, because the underlying issue is a CPU microarchitecture problem that predates WebAssembly and affects native code equally.
Why did two Wasmtime CVEs land as sandbox escapes in the same disclosure?
Because the sandbox boundary is implemented in the compiler backend, and a compiler bug in that backend can silently produce code that violates the guarantee the language spec promises. In CVE-2026-34987, the Winch baseline compiler mishandled a memory offset calculation, letting specially constructed guest wasm reach outside its allotted linear memory into host address space — a bug in code generation, not in the wasm bytecode a developer wrote. CVE-2026-34971 was subtler: Cranelift's aarch64 backend miscompiled a load of the shape load(iadd(base, ishl(index, amt))) when amt was a constant, masking it incorrectly so the address used for the bounds check diverged from the address actually loaded — a guest could pass the check and then read or write a different, attacker-chosen address. Both required specific, non-default configurations (Winch compiler; 64-bit memories with Spectre mitigations disabled on aarch64, respectively), which the Bytecode Alliance's April 9, 2026 advisory post is explicit about. The lesson generalizes past Wasmtime: every wasm runtime — Wasmer, V8's wasm engine, WAMR — is a large, independently-implemented piece of systems software, and the sandbox is only as sound as that specific codebase on that specific day.
How much risk comes from what a module is allowed to import, not from escaping the sandbox at all?
A large and often-overlooked share, because the sandbox only constrains what a module can do with instructions it executes directly — it says nothing about what an embedder chose to wire up as an import. A wasm module cannot open a file or a socket on its own; it can only call a function the host explicitly exposed to it. But an embedder that hands a module an unrestricted filesystem-write import, or a raw network-socket import, has recreated ambient authority by hand, and no amount of linear-memory bounds checking will stop the module from using it. This is a self-inflicted risk rather than a wasm defect, and it scales with how plugin-style architectures are built: a serverless platform or a browser extension host that grants broad imports "to keep things simple" has effectively given every third-party wasm plugin the access level of a native binary, defeating the reason it chose wasm sandboxing in the first place. Least-privilege import design — passing capabilities per-call rather than binding broad host objects once at instantiation — is the mitigation, and it is entirely on the embedder to get right.
Does WebAssembly have its own supply-chain risk, separate from JavaScript's?
The wasm module supply chain is real in principle but thin in documented incidents so far, which is worth stating precisely rather than inflating. There is no widely verified case of a malicious wasm binary published to a public registry and used to compromise downstream consumers, in contrast to JavaScript's well-documented history — for example the September 2025 compromise of the widely-used debug and chalk npm packages, part of an 18-package attack (via a phished maintainer account) that collectively saw over 2 billion weekly downloads, where attackers shipped Web3-wallet-draining code, a JavaScript-based incident, not a wasm one. That contrast doesn't mean wasm is immune: a compiled .wasm binary is opaque to casual review in a way JavaScript source rarely is, toolchains that compile C/C++/Rust to wasm inherit every vulnerability in their upstream dependency tree, and there is no wasm-specific equivalent yet of mature SBOM or provenance tooling for JS and Python ecosystems. The realistic posture is to treat wasm binaries the way you'd treat any compiled artifact: verify build provenance, pin toolchain versions, and don't assume a .wasm file is safe just because it will run inside a sandbox.