# `@suaveplan/api-sandbox-wasm` — WASI sandbox host **Package**: `@suaveplan/api-sandbox-wasm` **Status**: PROPOSED **Type**: new-package **Stage**: 8 — see umbrella `openspec/changes/api-plugin-foundation/` ## Why Sandboxed-tier plugins compiled to WebAssembly with the WASI ABI need a host that can load them with the narrowest possible capability set. WASI's capability model is at the file-descriptor and socket level, which lines up exactly with the manifest's `permissions.fs[]` (preopens) and `permissions.fetch[]` (outbound-socket allowlist). This package owns that translation. Per design doc §2.2 and the §14 open question on WASI granularity, the reference answer is: **read-only fs preopens of every listed path + outbound socket allowlist derived from `permissions.fetch[]`**. Writeable preopens require an explicit `rw:` rule prefix. This package locks that decision down. ## What changes - ADDED: `createWasmSandbox({ wasmModulePath, policy, proxies })` — instantiates the WASM module with a WASI snapshot wired to a `wasi_snapshot_preview1` (or preview2) implementation built from the `PermissionPolicy`. Returns a `WasmSandboxHandle`. - ADDED: `buildWasiPreopens(policy)` — maps `permissions.fs[]` to read-only preopen directories; `rw:` rules become read-write preopens. Paths outside the allowlist are not preopened, so the guest cannot open them. - ADDED: `buildWasiOutboundAllowlist(policy)` — maps `permissions.fetch[]` to a sockets allowlist consulted before each outbound socket connect; non-matching destinations raise `PERMISSION_DENIED` and emit `plugin.permission_denied_total{capability="fetch"}`. - ADDED: `installCapabilityImports(sandbox, proxies)` — exposes capability proxies as WASI host functions (or component-model imports), every call permission-checked first. - ADDED: `runWasmSandboxConformance(label, makeSandbox)` (`./testing`) — conformance suite pinning WASI-specific behaviour (preopen scoping, capability import permission gating, dispose semantics). - ADDED: Error codes (`SAND_WASM_*`): `INSTANTIATION_FAILED`, `WASI_VERSION_UNSUPPORTED`, `OUTBOUND_DENIED`, `PREOPEN_DENIED`. ## Out of scope - WASM build toolchain. This package consumes a pre-built `.wasm` file produced by the plugin author. - WASI preview2 component-model conversion utilities. The default supports preview1; preview2 is opt-in via host config. - Permission policy resolution (that's `api-sandbox-permissions`). - Worker spawning (that's `api-worker-host`). ## Source mapping Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`. | Requirement | Design doc section | |---|---| | WASI permission-model mapping | §2.2, §14 (resolved) | | Read-only preopens by default | §14 | | Outbound socket allowlist | §14, §5.2b | | `PERMISSION_DENIED` surfaced inside the plugin | §5.2b | ## Verification approach - `src/sandbox/sandbox.test.ts` — instantiate a fixture `.wasm` (small WASI hello-world built in CI), assert preopens reflect `permissions.fs[]`, assert outbound allowlist reflects `permissions.fetch[]`. - `src/preopens/preopens.test.ts` — read-only vs `rw:` rule prefix matrix. - `src/outbound/outbound.test.ts` — allowed / denied destination matrix; denied connect raises `SAND_WASM_OUTBOUND_DENIED` and never opens a socket. - `src/sandbox/sandbox.spec-conformance.test.ts` — observability: denials emit `plugin.permission_denied_total` with the right labels; `PERMISSION_DENIED` reaches the guest. - `src/testing/run-wasm-conformance.test.ts` — self-test of the harness against the fixture. - `tests/dep-graph.test.ts` — runtime deps include `api-types`, `api-sandbox-permissions`, `error`, `logger`. ## Migration impact None — additive only. `api-host` registers this when `host.json` `defaultSandbox = "wasm"`.