first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
# preopens
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Maps `policy.fs[]` rules onto a WASI preopen table. The WASI capability
model is file-descriptor-based: a guest can only open paths reachable
from a preopened directory, and the host controls whether each preopen
is read-only or read-write. This module owns that translation.
Paths outside the policy allowlist are NOT preopened, so the guest's
`path_open` cannot reach them — denial is enforced by the absence of
the preopen, not by a runtime check. Write access requires the `rw:`
rule prefix; the default is read-only.
## Features
- `rw:` prefix promotes a preopen to read-write; absence keeps it read-only.
- Globs `/*` and `/**` are stripped because WASI preopens are
directory-granular — file-level scoping is enforced by the WASI
runtime's resolved-path check.
- Deduplication: duplicate rules collapse, with `rw` winning over `ro`.
- `matchPreopen` returns the preopen for `path_open` gating, or
`undefined` for a miss / write-against-ro.
## Usage: Basic
```ts
import {
buildWasiPreopens,
resolvePermissionPolicy,
} from "@suaveplan/api-sandbox-wasm";
const policy = resolvePermissionPolicy({ fs: ["./uploads/*", "rw:./tmp"] });
const preopens = buildWasiPreopens(policy);
```
## Usage: Advanced
```ts
import { matchPreopen } from "@suaveplan/api-sandbox-wasm/preopens";
const matched = matchPreopen(preopens, "/abs/host/path/to/file", "rw");
if (matched === undefined) {
metrics.permissionDenied("fs", "preopen_miss_or_ro");
throw new SandWasmError(SAND_WASM_CODES.PREOPEN_DENIED, "denied");
}
```
## API Reference
- `buildWasiPreopens(policy): readonly WasiPreopen[]`
- `matchPreopen(preopens, hostPath, "ro" | "rw"): WasiPreopen | undefined`
- `WasiPreopen.{guestPath, hostPath, mode}`
## Implementation Notes
`hostPath` is computed via `node:path.resolve`, anchoring relative
rules against `process.cwd()` AT POLICY-BUILD TIME — operators must
build the policy in the directory the plugin expects to see. The
resolved path is used as the dedup key; rules that produce the same
absolute path collapse, with `rw` winning over `ro` because privilege
escalation is more visible than silent downgrade.
WASI's preview1 file-rights bitmask is rich (read, write, allocate,
truncate, etc.) — the implementation here intentionally uses a
two-state mode so policy authoring stays readable. Sites that need
finer granularity should layer their own bitmask check on top.