Files
skills/docs/api-stack/api/api-sandbox-wasm/src/sandbox/sandbox.md
2026-05-29 08:20:56 +01:00

2.7 KiB

sandbox

(no submodules)

Purpose

Top-level entry point for the WASM sandbox host. createWasmSandbox compiles (or accepts a pre-compiled) WASM module, validates its WASI imports against the supported-version allowlist, builds the preopen table and outbound allowlist from the PermissionPolicy, wraps every capability import in the permission checker, and instantiates the guest under those constraints.

The returned WasmSandboxHandle exposes the WebAssembly instance plus the policy bindings (preopens, outbound) for diagnostic consumers, and a dispose() method that is idempotent — calling it twice is a no-op. Memory is bound automatically when the guest exports a memory of type WebAssembly.Memory.

Features

  • Accepts a pre-compiled WebAssembly.Module or raw bytes.
  • WASI version pre-check via detectWasiVersion.
  • Wraps WebAssembly.compile / WebAssembly.instantiate failures in SAND_WASM_INSTANTIATION_FAILED, preserving the underlying cause for operator triage.
  • Idempotent dispose() — the WebAssembly instance becomes eligible for GC once the handle is released.

Usage: Basic

const handle = await createWasmSandbox({
    pluginName: "stripe-checkout",
    policy,
    capabilities: [],
    checker,
    wasmBytes: await readFile("./plugin.wasm"),
});
try {
    (handle.instance.exports._start as () => void)();
} finally {
    handle.dispose();
}

Usage: Advanced

// Hot-reload path: re-use the compiled module across reloads.
const module = await WebAssembly.compile(bytes);
let handle = await createWasmSandbox({ ..., wasmModule: module });
hotReloadBus.on("reload", async () => {
    handle.dispose();
    handle = await createWasmSandbox({ ..., wasmModule: module });
});

API Reference

  • createWasmSandbox(options): Promise<WasmSandboxHandle>
  • CreateWasmSandboxOptions.{pluginName, policy, capabilities, checker, wasmModule | wasmBytes, wasiDeps?, instruments?}
  • WasmSandboxHandle.{instance, preopens, outbound, wasi, instruments, dispose(), disposed}

Implementation Notes

The pre-compiled-vs-bytes split exists because hot-reload paths cache the WebAssembly.Module across reloads to avoid re-paying the validator cost on every cycle. Compilation is significantly more expensive than instantiation, and operators have explicitly asked for the option.

dispose() does NOT call any fd_close on the preopens because the preopen layer in this minimal snapshot does not hold open file descriptors — each path_open is a fresh stat against the host filesystem. A richer WASI implementation that holds open fds would need to track them in the handle and close them in dispose().