Safeguard
Industry Analysis

How the WASI security model constrains system access for ...

WASI's capability model gives Wasm modules only the exact files, sockets, and resources they're explicitly granted—but misconfiguration and runtime bugs still leave real gaps to close.

James
Principal Security Architect
7 min read

Wasm was supposed to solve the "run untrusted code safely" problem once and for all, and for the most part it does—inside the sandbox. The catch is that no module runs in a vacuum: it needs to read a config file, open a socket, or write a log somewhere on the host. That's the job of WASI, the WebAssembly System Interface, and the reason WASI security has become a serious topic for anyone shipping Wasm into production rather than a toy in a browser tab. WASI's answer isn't a permissions dialog or a policy file bolted on after the fact—it's capability-based security baked into the interface itself: a module can only touch the exact file descriptors, directories, and sockets its host explicitly hands it at instantiation time, nothing more. That design choice is elegant, but it is not automatic protection, and the gaps between the model and how teams actually deploy it are where real Wasm system interface risk lives.

What is WASI security and why does it matter now?

WASI security is the practice of controlling what a WebAssembly module can do to the outside world—files, network, clocks, randomness—through an explicit, host-granted capability list rather than ambient authority. The project started inside Mozilla in early 2019 and moved to the newly formed Bytecode Alliance that November, when Mozilla, Fastly, Intel, and Red Hat pooled their runtime work under one governance umbrella. It matters now because Wasm has left the browser: Fastly's Compute platform, wasmCloud, Spin, and countless plugin systems (from database extensions to CI runners) execute third-party or partially-trusted Wasm modules server-side. A browser tab has the DOM sandbox and same-origin policy backing it up. A server-side Wasm module has WASI, the host runtime's implementation of WASI, and whatever mistakes get made configuring both. Get that wrong and you've reintroduced the exact "arbitrary code with host privileges" problem Wasm was built to avoid.

How does the WASI capability model actually work?

It works by refusing to give a module any ambient authority at all—every resource has to be handed over explicitly as an unforgeable token before the module can use it. This isn't a new idea; it traces back to Dennis and Van Horn's 1966 paper on capability-based protection and shows up later in systems like FreeBSD's Capsicum (2010) and Google Fuchsia. WASI's version is concrete and unglamorous: instead of calling open("/etc/passwd") and having the OS decide whether that's allowed, a module receives pre-opened file descriptors chosen by the host at startup—commonly exposed in Wasmtime as --dir=./data::/data, mapping a specific host directory to a specific guest path. If a module never receives a descriptor for a resource, there is no syscall, no path traversal trick, and no privilege-escalation bug that gets it there—the capability simply doesn't exist inside the sandbox. That's the core difference from Unix-style discretionary access control: permissions aren't checked at call time against a global namespace, they're constrained by what's reachable in the first place.

What can a malicious Wasm module actually do under WASI?

Under a correctly configured WASI host, a malicious module can only do what its narrow capability set allows—read the two directories it was preopened into, write to the one socket it was handed, and nothing else on the filesystem or network. It cannot enumerate /etc, cannot open arbitrary TCP connections, cannot fork processes, and cannot call ambient OS APIs, because WASI simply doesn't expose those as importable functions in the first place; there's no exec, no raw syscall table, no pointer into host memory outside its own linear memory. This is a meaningfully smaller blast radius than a native shared library or a Python plugin with import os, both of which inherit the full authority of the host process by default. The honest caveat: "correctly configured" is doing a lot of work in that sentence, and it's the gap attackers and auditors both focus on.

Where does Wasm system interface risk still creep in?

Wasm system interface risk creeps in mainly through overly broad host configuration, not through breaking the capability model itself. The two most common patterns Safeguard's engineers see: preopening a directory far wider than the module needs—mounting / or a shared /tmp "just to be safe" turns the capability model back into ambient authority—and reusing the same host process (and therefore the same capability set) across multiple tenants or plugin instances instead of instantiating a fresh, narrowly scoped store per workload. There's also a runtime-layer risk that sits underneath WASI entirely: the Bytecode Alliance's public security advisory database has logged more than a dozen Wasmtime and Cranelift advisories since 2021, the large majority being memory-safety or miscompilation bugs in the JIT rather than WASI capability leaks. A perfect capability model doesn't help if the compiler generating the sandboxed code has a bounds-check bug underneath it. Supply chain provenance matters here too—a module pulled from an unverified registry can request capabilities that look reasonable individually (read config, open one socket) but combine into a data-exfiltration path nobody reviewed.

How mature is WASI security today?

WASI security matured substantially with the January 2024 release of WASI Preview 2, shipped alongside Wasmtime 17 and built on the Component Model, which replaced the original ad hoc "Preview 1" syscall set with a properly versioned, interface-typed standard. Preview 1, the version most production deployments still ran through 2023, was explicitly labeled a stopgap by its own authors—it worked, but its capability boundaries around things like clock and random-number access were coarser than the design ultimately intended. Preview 2 tightens that by defining capabilities as composable, typed "worlds" (wasi:filesystem, wasi:sockets, wasi:clocks) that a host assembles per module, making it far easier to audit exactly what surface a given module can reach. The tradeoff is fragmentation risk during the transition: a security review done against a Preview 1 deployment doesn't automatically transfer to a Preview 2 one, and mixed-version fleets are now common in the wild.

Does capability-based security eliminate the need for other controls?

No—capability-based security reduces the attack surface a module can reach, but it doesn't replace code review, dependency provenance, or runtime monitoring, and treating it as a full substitute is the most common mistake teams make. A module scoped to one read-only config directory and one outbound socket is still capable of exfiltrating whatever it reads through that socket, still capable of consuming unbounded CPU or memory if the host doesn't set fuel or memory limits, and still capable of shipping a supply-chain-compromised dependency that behaves fine in testing and misbehaves in production. WASI narrows what's technically reachable; it says nothing about whether the specific capabilities granted are the right ones for the actual workload, and that judgment call happens at build and deploy time, not at the interface layer.

How Safeguard Helps

Safeguard treats the WASI capability boundary as one control among several, not a finish line. For teams building or consuming Wasm modules, that means scanning the module supply chain before a capability grant is even written—verifying build provenance, checking dependencies against known-vulnerable versions, and flagging modules whose declared imports request broader WASI capabilities (full filesystem access, unrestricted sockets) than their stated function needs. Safeguard also audits host-side configuration drift, the --dir mounts and preopen lists that tend to widen quietly over time as teams debug production issues and forget to narrow scope back down afterward. And because runtime bugs sit underneath the capability model rather than inside it, Safeguard tracks Bytecode Alliance and Wasmtime/Wasmer advisories against your deployed runtime versions, so a Cranelift memory-safety fix doesn't sit unpatched for months because nobody was watching that feed. The result is a supply chain view that covers the module, its declared capabilities, and the runtime enforcing them—the three layers that all have to hold for WASI security to mean what it promises.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.