Skip to content

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.

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 has gone through two major revisions:

Preview 1 (0.1)Preview 2 (0.2)
StatusStable, widely supportedStable since early 2024
Interface styleFlat C-like imports (fd_write, path_open)Component Model WIT interfaces
Target triplewasm32-wasi / wasm32-wasip1wasm32-wasip2
NetworkingNot in core (vendor extensions)wasi:sockets in core
ToolchainRust stable, WASI SDKcargo 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.

Add the target once, then build normally:

Terminal window
# Install the WASI target
rustup target add wasm32-wasip1
# Build a release binary
cargo build --target wasm32-wasip1 --release
# Output: target/wasm32-wasip1/release/my_app.wasm

A 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.

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:

Terminal window
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
Capability grants flow into the module at startup
OptionBenefitCost
WASI capability-based securityFar safer than POSIX ambient authority — a module can only use resources the runtime explicitly hands itNot fully POSIX-compatible; many native libraries that assume ambient filesystem/network access don’t port cleanly
Server-side Wasm (a WASI runtime) vs. containersMicrosecond-scale cold starts, a good fit for edge/serverless work that spins up oftenContainer ecosystems still have more mature tooling, orchestration, and library support
  • Expecting filesystem or network access to “just work” like a normal native program, instead of explicitly granting it via --dir or --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-wasip2 today 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.

In WASI, how does a module gain access to a directory on the host filesystem?
Which Rust target triple compiles to WASI Preview 1?
What is the WASI entry-point function that runtimes look for?
What is the main security advantage of WASI capability-based security over POSIX ambient authority?