Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# `@suaveplan/api-sandbox-permissions` — permission model for sandboxed-tier plugins
**Package**: `@suaveplan/api-sandbox-permissions`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 8 (Sandboxing + cross-cutting middleware + authoring helpers) — see umbrella `openspec/changes/api-plugin-foundation/`
## Why
Sandboxed-tier plugins (manifest `isolation: "sandboxed"`) execute untrusted or third-party code and MUST NOT have ambient access to the filesystem, the network, or unbounded compute. Per design doc §2.2 the sandboxed tier is implemented as a worker thread plus either `vm.Context` (`api-sandbox-vm`) or WASI (`api-sandbox-wasm`); both hosts share the same permission model. Centralising that model in a single package keeps the policy in one place, makes denial behaviour observable, and lets both sandbox hosts gate capability calls through the same code path.
Per §5.2b every sandboxed capability call passes through this layer before reaching the proxy surface — denials emit `plugin.permission_denied_total{plugin, capability, reason}` and surface as `PERMISSION_DENIED` errors inside the plugin.
## What changes
- ADDED: `PermissionPolicy` — value object holding the resolved `permissions.fs[]` allowlist, `permissions.fetch[]` allowlist, and `permissions.budgets` (cpuPercent / memoryMb / wallClockMs). Built from `PermissionsManifest` (`api-types`).
- ADDED: `PermissionChecker.checkFs(path, mode)` — verifies a host-proxied filesystem path matches the allowlist (glob match against `permissions.fs[]`); returns `Allow | Deny<reason>`.
- ADDED: `PermissionChecker.checkFetch(url)` — verifies an outbound destination host matches `permissions.fetch[]`; supports exact host and `*.suffix` patterns.
- ADDED: `PermissionChecker.checkCapability(capabilityId, payload)` — for `db` / `secrets` / `events` payload-shape gating per §5.2b (secret name in manifest, event topic in declared `publishes` / `subscribes`, DB query scoped to plugin's declared `database`).
- ADDED: `BudgetTracker` — tracks per-request CPU sample, resident-memory sample, and wall-clock elapsed; raises `BUDGET_EXCEEDED` when any budget is breached. Counters are caller-supplied per `Clock` / sampling hooks.
- ADDED: `PERMISSION_DENIED` and `BUDGET_EXCEEDED` error codes (`SAND_PERM_*` registry) plus a redaction-safe `details` schema.
- ADDED: `plugin.permission_denied_total` and `plugin.budget_exceeded_total` instrument descriptors so both sandbox hosts emit identical metric names with identical label sets (`plugin`, `capability`, `reason`).
- ADDED: `runPermissionsConformance(label, makeChecker)` testing harness (subpath `./testing`) so `api-sandbox-vm`, `api-sandbox-wasm`, and any future sandbox host exercise the same allow / deny / budget invariants.
## Out of scope
- The actual `vm.Context` runtime (`api-sandbox-vm`) and the WASI runtime (`api-sandbox-wasm`). Both consume this package; neither is bundled here.
- The host-proxy implementations for `fs` and `fetch` — they live in `api-host` (main-process side) and only call `PermissionChecker` before performing the underlying operation.
- Authorisation across plugins (that is `api-rbac`). This package gates a plugin's own capability calls; RBAC gates a request's principal scopes.
- CPU/memory sampling primitives — supplied by the worker host; this package only owns the budget tracker that aggregates samples and decides allow / deny.
## Source mapping
Genesis-original. Source of design intent: `docs/superpowers/specs/2026-05-13-api-plugin-foundation-design.md`.
| Requirement | Design doc section |
|---|---|
| `permissions.fs` / `permissions.fetch` allowlists | §3 (`permissions` block) |
| Budget enforcement (CPU / memory / wall-clock) | §3 (`permissions.budgets`) |
| Per-capability gating (db / secrets / events / fs / fetch) | §5.2b |
| `plugin.permission_denied_total{plugin, capability, reason}` metric | §5.2b, §12.4 |
| `PERMISSION_DENIED` error code surfaced inside the plugin | §5.2b |
## Verification approach
- `src/policy/policy.test.ts` — `PermissionPolicy` construction from manifest, normalisation of paths, glob compilation.
- `src/checker/fs.test.ts`, `src/checker/fetch.test.ts`, `src/checker/capability.test.ts` — allow / deny matrices, including edge cases (path traversal, port-in-host, scheme rejection).
- `src/budget/budget.test.ts` — budget breach behaviour, monotonic clock progression, multi-budget interaction.
- `src/checker/checker.spec-conformance.test.ts` — pins the observable contract: every deny path increments `plugin.permission_denied_total` exactly once AND raises `PERMISSION_DENIED` with the documented `details` shape (verified against a real in-memory `MeterProvider`).
- `src/testing/run-permissions-conformance.test.ts` — the shipped harness exercised against an in-package fixture checker, so adapters that consume it know exactly what they must satisfy.
- `tests/dep-graph.test.ts` — pins zero `@suaveplan/*` runtime deps beyond `api-types`, `error`, `types`, and `opentelemetry`.
## Migration impact
None — additive only. `api-sandbox-vm` and `api-sandbox-wasm` (Stage 8 siblings) take this as a dependency.