Skip to main content
Technology & EngineeringWebassembly222 lines

Wasi

WebAssembly System Interface (WASI) for portable system-level access including filesystem, networking, and clocks

Quick Summary30 lines
You are an expert in the WebAssembly System Interface (WASI) for building WebAssembly applications.

## Key Points

- **Use `wasm32-wasip1` for maximum compatibility** — Preview 1 is supported by all major runtimes. Use Preview 2 only when you need its specific features (component model, typed interfaces).
- **Test with multiple runtimes** — Wasmtime, Wasmer, and WasmEdge may have subtly different WASI implementations. Test on your deployment target.
- **Prefer standard library I/O** — in Rust and C, standard `read`/`write`/`open` calls are automatically mapped to WASI. Avoid calling WASI functions directly unless necessary.
- **Handle missing capabilities gracefully** — if a pre-opened directory is not provided, file operations will fail. Check for errors rather than assuming access.
- **Forgetting `--dir` pre-opens** — WASI modules cannot access any filesystem path unless the host explicitly pre-opens it. Missing pre-opens cause `ENOTCAPABLE` or `ENOENT` errors.
- **Assuming network access** — WASI Preview 1 has no networking support. TCP/UDP requires Preview 2's `wasi:sockets` or runtime-specific extensions.
- **Preview 1 vs Preview 2 target mismatch** — modules compiled for `wasm32-wasip1` will not work with a Preview 2-only host configuration, and vice versa. Check your runtime's documentation.
- **No threads in WASI Preview 1** — `wasm32-wasip1` does not support `std::thread`. Code that spawns threads will fail to compile or panic at runtime.
- **Clock resolution differences** — `clock_time_get` resolution varies by host. Do not depend on nanosecond precision being accurate across all runtimes.
- **Assuming network access in Preview 1** — WASI Preview 1 has no networking APIs; TCP/UDP requires Preview 2's `wasi:sockets` or runtime-specific extensions like WasmEdge's HTTP plugin.
- **Spawning threads in Preview 1 targets** — `wasm32-wasip1` does not support `std::thread` or multi-threading; code that depends on thread spawning will fail to compile or trap at runtime.

## Quick Example

```bash
rustup target add wasm32-wasip1
```

```bash
cargo build --target wasm32-wasip1 --release

# Run with Wasmtime, granting access to /data directory
wasmtime run --dir /data::./local_data --env USER=alice target/wasm32-wasip1/release/app.wasm
```
skilldb get webassembly-skills/WasiFull skill: 222 lines
Paste into your CLAUDE.md or agent config

WASI — WebAssembly

You are an expert in the WebAssembly System Interface (WASI) for building WebAssembly applications.

Overview

WASI (WebAssembly System Interface) is a standardized set of APIs that give WebAssembly modules portable access to operating system functionality such as filesystems, clocks, random number generation, and networking. WASI enables Wasm to run outside the browser as a secure, sandboxed application runtime. All capabilities are explicitly granted by the host, following a capability-based security model.

Core Concepts

WASI Versioning

  • WASI Preview 1 (wasip1) — the stable, widely-supported snapshot. Provides fd_read, fd_write, path_open, clock_time_get, random_get, and related syscall-like functions. Supported by Wasmtime, Wasmer, Node.js, and others.
  • WASI Preview 2 (wasip2) — the component-model-based redesign. Uses WIT (Wasm Interface Types) to define typed interfaces. Introduces wasi:filesystem, wasi:sockets, wasi:http, and wasi:cli worlds. Under active development with growing runtime support.

Capability-Based Security

WASI does not grant blanket access to the host system. Every capability (directory access, network socket, environment variable) must be explicitly provided by the host at instantiation time. A module cannot access anything it was not given.

Key Interfaces (Preview 1)

ModulePurpose
wasi_snapshot_preview1Filesystem, stdio, clocks, random, args, environ

Key Interfaces (Preview 2 / WIT)

WorldPurpose
wasi:cli/runCommand-line application entry
wasi:filesystemFile and directory operations
wasi:socketsTCP/UDP networking
wasi:httpIncoming and outgoing HTTP
wasi:randomCryptographic random bytes
wasi:clocksMonotonic and wall clocks

Implementation Patterns

Rust with WASI Preview 1

rustup target add wasm32-wasip1
// src/main.rs — standard Rust I/O works under WASI
use std::fs;
use std::io::{self, BufRead};

fn main() {
    // Read environment variable
    let name = std::env::var("USER").unwrap_or_else(|_| "world".into());
    println!("Hello, {}!", name);

    // Read a file (host must pre-open the directory)
    match fs::read_to_string("/data/config.txt") {
        Ok(contents) => println!("Config: {}", contents),
        Err(e) => eprintln!("Cannot read config: {}", e),
    }

    // Read from stdin
    let stdin = io::stdin();
    for line in stdin.lock().lines() {
        let line = line.unwrap();
        if line.is_empty() { break; }
        println!("Echo: {}", line);
    }
}
cargo build --target wasm32-wasip1 --release

# Run with Wasmtime, granting access to /data directory
wasmtime run --dir /data::./local_data --env USER=alice target/wasm32-wasip1/release/app.wasm

C with WASI (using wasi-sdk)

// main.c
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    printf("Arguments:\n");
    for (int i = 0; i < argc; i++) {
        printf("  [%d] %s\n", i, argv[i]);
    }

    const char *val = getenv("MY_VAR");
    if (val) {
        printf("MY_VAR = %s\n", val);
    }

    FILE *f = fopen("/data/input.txt", "r");
    if (f) {
        char buf[256];
        while (fgets(buf, sizeof(buf), f)) {
            printf("Line: %s", buf);
        }
        fclose(f);
    }

    return 0;
}
# Compile with wasi-sdk
/opt/wasi-sdk/bin/clang --sysroot=/opt/wasi-sdk/share/wasi-sysroot \
  -o app.wasm main.c

# Run
wasmtime run --dir /data::./data --env MY_VAR=hello app.wasm -- arg1 arg2

Running WASI Modules Programmatically (Wasmtime Rust API)

use wasmtime::*;
use wasmtime_wasi::preview1::WasiP1Ctx;
use wasmtime_wasi::WasiCtxBuilder;

fn main() -> anyhow::Result<()> {
    let engine = Engine::default();
    let mut linker = Linker::<WasiP1Ctx>::new(&engine);
    wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |t| t)?;

    let wasi = WasiCtxBuilder::new()
        .inherit_stdio()
        .preopened_dir("./data", "/data", DirPerms::all(), FilePerms::all())?
        .env("USER", "alice")
        .args(&["app", "--verbose"])
        .build_p1();

    let mut store = Store::new(&engine, wasi);
    let module = Module::from_file(&engine, "app.wasm")?;
    linker.module(&mut store, "", &module)?;

    let func = linker.get_default(&mut store, "")?;
    func.call(&mut store, &[], &mut [])?;

    Ok(())
}

WASI Preview 2 with WIT

// wit/world.wit
package my:app;

world my-command {
    import wasi:filesystem/types@0.2.0;
    import wasi:cli/stdin@0.2.0;
    import wasi:cli/stdout@0.2.0;

    export wasi:cli/run@0.2.0;
}

Node.js WASI Support

import { readFile } from "node:fs/promises";
import { WASI } from "node:wasi";

const wasi = new WASI({
  version: "preview1",
  args: ["app", "--verbose"],
  env: { USER: "alice" },
  preopens: { "/data": "./local_data" },
});

const module = await WebAssembly.compile(await readFile("app.wasm"));
const instance = await WebAssembly.instantiate(module, wasi.getImportObject());
wasi.start(instance);

Best Practices

  • Grant minimal capabilities — only pre-open directories and provide environment variables that the module actually needs. WASI's security model is only effective if capabilities are scoped tightly.
  • Use wasm32-wasip1 for maximum compatibility — Preview 1 is supported by all major runtimes. Use Preview 2 only when you need its specific features (component model, typed interfaces).
  • Test with multiple runtimes — Wasmtime, Wasmer, and WasmEdge may have subtly different WASI implementations. Test on your deployment target.
  • Prefer standard library I/O — in Rust and C, standard read/write/open calls are automatically mapped to WASI. Avoid calling WASI functions directly unless necessary.
  • Handle missing capabilities gracefully — if a pre-opened directory is not provided, file operations will fail. Check for errors rather than assuming access.

Common Pitfalls

  • Forgetting --dir pre-opens — WASI modules cannot access any filesystem path unless the host explicitly pre-opens it. Missing pre-opens cause ENOTCAPABLE or ENOENT errors.
  • Path mapping confusion — the --dir host_path::guest_path syntax maps a host directory to a guest-visible path. Getting these reversed causes "file not found" errors even when the file exists on the host.
  • Assuming network access — WASI Preview 1 has no networking support. TCP/UDP requires Preview 2's wasi:sockets or runtime-specific extensions.
  • Preview 1 vs Preview 2 target mismatch — modules compiled for wasm32-wasip1 will not work with a Preview 2-only host configuration, and vice versa. Check your runtime's documentation.
  • No threads in WASI Preview 1wasm32-wasip1 does not support std::thread. Code that spawns threads will fail to compile or panic at runtime.
  • Clock resolution differencesclock_time_get resolution varies by host. Do not depend on nanosecond precision being accurate across all runtimes.

Core Philosophy

WASI's capability-based security model is its defining feature and its greatest strength. Unlike traditional operating systems where processes have ambient authority to access any file or network resource, WASI modules have no capabilities by default. Every directory, environment variable, and network socket must be explicitly granted by the host. This is not a limitation to work around — it is a security architecture to embrace. Design modules that request minimal capabilities and handle missing ones gracefully.

WASI makes WebAssembly a universal binary format for server-side code. A module compiled to wasm32-wasip1 runs on Wasmtime, Wasmer, WasmEdge, and Node.js without recompilation. This portability is valuable for plugin systems, serverless functions, and edge computing, where the execution environment varies. Write standard library I/O (not runtime-specific extensions) to maximize the number of hosts that can run your module.

Preview 1 is stable and widely supported. Preview 2 is the future, introducing the Component Model and typed WIT interfaces, but runtime support is still maturing. For production workloads today, target wasm32-wasip1. For forward-looking designs, experiment with Preview 2 and WIT to define typed interfaces that will compose cleanly across language boundaries.

Anti-Patterns

  • Forgetting --dir pre-opens — WASI modules cannot access any filesystem path unless the host explicitly pre-opens it; running without pre-opens causes cryptic ENOTCAPABLE or ENOENT errors on file operations.

  • Assuming network access in Preview 1 — WASI Preview 1 has no networking APIs; TCP/UDP requires Preview 2's wasi:sockets or runtime-specific extensions like WasmEdge's HTTP plugin.

  • Granting more capabilities than needed — pre-opening the root directory or passing the entire environment defeats WASI's security model; grant only the specific directories and variables the module requires.

  • Using WASI-specific APIs when standard library I/O works — calling wasi_snapshot_preview1 functions directly when std::fs::read_to_string or printf achieve the same result reduces portability; prefer standard library I/O.

  • Spawning threads in Preview 1 targetswasm32-wasip1 does not support std::thread or multi-threading; code that depends on thread spawning will fail to compile or trap at runtime.

Install this skill directly: skilldb add webassembly-skills

Get CLI access →