100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
WebAssembly

Wasm Security Model

How WebAssembly's linear-memory sandboxing, structured control flow, and capability-based WASI design together minimize the attack surface of untrusted code.

Advanced TopicsIntermediate9 min readJul 10, 2026
Analogies

The Sandboxing Foundation

Every piece of memory a WebAssembly module can touch lives inside its own linear memory, a contiguous, bounds-checked array of bytes that the runtime allocates and that grows only in whole 64KiB pages via explicit memory.grow calls. Every load and store instruction is bounds-checked against the current memory size before it executes, so a buggy or malicious module cannot read or write arbitrary host process memory the way a native buffer overflow can corrupt adjacent heap data; an out-of-bounds access simply traps and halts execution. Combined with the fact that Wasm has no instructions capable of directly manipulating a raw machine pointer or jumping to an arbitrary code address, this makes memory-safety violations inside the sandbox contained: a compromised Wasm module can corrupt its own linear memory, but it cannot use that corruption to escape into the host's address space without a bug in the host embedding itself.

🏏

Cricket analogy: This is like a boundary rope strictly enforced by third umpires with ball-tracking, where a fielder can dive and misjudge inside the rope but physically cannot alter what happens beyond it; linear memory bounds checks are that rope for every read and write.

Structured Control Flow and Type-Checked Calls

Unlike native machine code, where a corrupted return address or function pointer can redirect execution to attacker-controlled bytes (classic ROP/JOP exploitation), WebAssembly has no raw jump instructions at all; control flow is expressed only through structured constructs, if/else, loop, block, and br/br_if for branching within them, and call/call_indirect for invocation, all validated at load time to reference only valid, well-typed targets. Indirect calls through a function table are checked against the callee's declared type signature at call time, so even if an attacker could corrupt a table index, the runtime would trap rather than execute a mismatched-signature function, which closes off an entire class of control-flow-hijacking exploits that plague native code. This validation happens once, eagerly, when the module is loaded, which is also why a hostile module can be rejected outright before a single instruction executes if it fails to type-check.

🏏

Cricket analogy: This is like DRS reviewing every LBW appeal against a strict, pre-agreed rulebook before the decision stands; you cannot simply overturn a decision arbitrarily, the review must match one of the sanctioned outcomes, the way call_indirect must match a declared type signature.

Capability-Based Security with WASI

A Wasm module has zero ambient authority: it cannot open a file, make a network connection, or read an environment variable unless the host explicitly imports a function granting that capability. WASI formalizes this as capability-based security, most visibly through preopened directory handles: a host running a module with Wasmtime's --dir=./data flag grants that module a file descriptor scoped only to the ./data subtree, and the module can never construct a path like ../../etc/passwd to escape it, because it never receives a capability referencing anything outside the preopen in the first place, not because of a runtime string check on '..' segments. This is a fundamentally different security posture from a traditional process, which by default inherits broad ambient access to the filesystem and network and must be actively restricted after the fact with something like seccomp or a container boundary; Wasm/WASI instead starts from zero access and grants capabilities explicitly, one handle at a time.

🏏

Cricket analogy: This is like a substitute fielder brought on only for water breaks being handed a bib that grants them zero authority to bowl, bat, or make dismissal appeals; capabilities are explicitly assigned per role, not assumed by default just because they're on the field.

bash
# Run a Wasm module with a capability scoped to one directory only
wasmtime run --dir=./data ./app.wasm

# The module has NO access outside ./data — not because '..' is blocked,
# but because it was never handed a capability referencing anything else.

# Grant a network capability explicitly (WASI sockets, preview-level API)
wasmtime run --dir=./data --tcplisten=127.0.0.1:8080 ./server.wasm

Control-flow integrity in Wasm is a structural property, not a bolted-on mitigation: because there is no instruction to jump to an arbitrary address and call_indirect is type-checked against the function table at call time, entire exploit classes like return-oriented programming (ROP) that dominate native memory-corruption exploitation simply have no analog in valid Wasm bytecode.

The Wasm sandbox protects against memory-corruption escape and unauthorized capability access, but it does not protect against side-channel attacks like Spectre-style speculative execution leaks across the sandbox boundary; this is precisely why SharedArrayBuffer (needed for Wasm threads) requires cross-origin isolation via COOP/COEP headers in browsers.

  • Linear memory is bounds-checked on every access, containing memory-safety bugs inside the module's own sandbox.
  • Wasm has no raw jump or pointer-dereference instructions; control flow is limited to structured, validated constructs.
  • call_indirect is type-checked against the function table at call time, eliminating classic control-flow-hijacking exploit classes like ROP.
  • Module validation happens eagerly at load time, rejecting malformed or ill-typed modules before execution.
  • WASI implements capability-based security: a module gets zero ambient authority and only the specific handles (e.g. preopened directories) the host explicitly grants.
  • Preopen-based sandboxing works because the module never receives a capability referencing anything outside the preopen, not via runtime path-string filtering.
  • The sandbox does not defend against side-channel attacks like Spectre; that's why SharedArrayBuffer requires COOP/COEP cross-origin isolation.

Practice what you learned

Was this page helpful?

Topics covered

#WebAssembly#WebAssemblyStudyNotes#WebDevelopment#WasmSecurityModel#Wasm#Security#Model#Sandboxing#StudyNotes#SkillVeris