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.Moduleor raw bytes. - WASI version pre-check via
detectWasiVersion. - Wraps
WebAssembly.compile/WebAssembly.instantiatefailures inSAND_WASM_INSTANTIATION_FAILED, preserving the underlyingcausefor 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().