WASI Deep Dive
WASI does not just expose system calls — it redesigns how a program is granted access to system resources. Instead of the POSIX model where a process inherits ambient authority from its user ID, WASI uses capability-based security: a module can only use a resource if the runtime explicitly passes a capability for it.
Capability-based security
Section titled “Capability-based security”In a traditional Unix process, opening /etc/passwd succeeds as long as the process’s UID has read permission. There is no sandboxing at the application boundary.
In WASI the module has no ambient access at all. The runtime acts as a gatekeeper:
- To read from the filesystem, the host must pass a pre-opened directory file descriptor via
--dir. - To access an environment variable, the host must allow it via
--env. - By default, a WASI module has zero filesystem access, zero network access, and zero environment variable access.
This means you can safely run untrusted .wasm binaries — the worst a compromised module can do is limited to exactly what it was handed.
WASI Preview 1 vs Preview 2
Section titled “WASI Preview 1 vs Preview 2”WASI has gone through two major revisions:
| Preview 1 (0.1) | Preview 2 (0.2) | |
|---|---|---|
| Status | Stable, widely supported | Stable since early 2024 |
| Interface style | Flat C-like imports (fd_write, path_open) | Component Model WIT interfaces |
| Target triple | wasm32-wasi / wasm32-wasip1 | wasm32-wasip2 |
| Networking | Not in core (vendor extensions) | wasi:sockets in core |
| Toolchain | Rust stable, WASI SDK | cargo component, wasm-tools |
For most production work today, Preview 1 (wasm32-wasip1) is still a safe, boring default. Preview 2 (wasm32-wasip2) is no longer brand-new tooling, though — in the Rust compiler both wasm32-wasip1 and wasm32-wasip2 are Tier 2 targets today, and WASI 0.2 has been the stable release since January 2024. A WASI 0.3 (Preview 3) is now in preview, replacing 0.2’s explicit polling/streams model with native async support via the Component Model’s future/stream types — it is not yet the stable release, so treat any “0.3” claim you see elsewhere as forward-looking, not shipped.
Compiling Rust to WASI
Section titled “Compiling Rust to WASI”Add the target once, then build normally:
# Install the WASI targetrustup target add wasm32-wasip1
# Build a release binarycargo build --target wasm32-wasip1 --release# Output: target/wasm32-wasip1/release/my_app.wasmA minimal Rust main.rs for WASI:
fn main() { println!("Hello from WASI!"); let args: Vec<String> = std::env::args().collect(); println!("Args: {:?}", args);}println! maps to the WASI fd_write import on file descriptor 1 (stdout). std::env::args() uses the WASI args_get / args_sizes_get imports. Both are part of Preview 1.
The _start entry point
Section titled “The _start entry point”When wasmtime (or any WASI runtime) executes a .wasm binary it looks for an exported function named _start. This is the WASI equivalent of main().
The Rust compiler generates _start automatically when you compile with a main function and the wasm32-wasip1 target. You can verify this with wasm-objdump:
wasm-objdump -x target/wasm32-wasip1/release/my_app.wasm | grep export# ...# - func[N] <_start> -> "_start"flowchart TD
RT["wasmtime / wasmer\n(runtime)"]
subgraph "Capability grants"
D["--dir /data\n(pre-opened fd)"]
E["--env KEY=VAL"]
A["argv via --"]
end
RT -->|"pre-opens"| D
RT -->|"injects"| E
RT -->|"passes"| A
D --> M["Wasm Module\n(_start)"]
E --> M
A --> M Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| WASI capability-based security | Far safer than POSIX ambient authority — a module can only use resources the runtime explicitly hands it | Not fully POSIX-compatible; many native libraries that assume ambient filesystem/network access don’t port cleanly |
| Server-side Wasm (a WASI runtime) vs. containers | Microsecond-scale cold starts, a good fit for edge/serverless work that spins up often | Container ecosystems still have more mature tooling, orchestration, and library support |
Common mistakes
Section titled “Common mistakes”- Expecting filesystem or network access to “just work” like a normal native program, instead of explicitly granting it via
--diror--env— the module fails silently or throws a permission error - Assuming WASI Preview 1 is fully POSIX-compatible and trying to port a native library that assumes ambient access, unmodified
- Not tracking that Preview 2 and the Component Model are still evolving standards — planning production on
wasm32-wasip2today means being ready for breaking interface changes later
💡 Real-world example
Fastly Compute runs untrusted customer code as Wasm modules with WASI-style capability sandboxing on its own edge network, giving each request a safe sandbox without relying on container isolation.