# Permission checker (in-package default) _(no submodules)_ ## Purpose `createPermissionChecker` compiles a `PermissionsManifest` (from `@suaveplan/api-types`) into a structural `PermissionChecker` that the `vm.Context` sandbox host consults before forwarding any filesystem, outbound-HTTP, or capability call to the host. The default implementation lives here so the package ships functionally complete even before the full `@suaveplan/api-sandbox-permissions` package arrives . This module exists to make policy enforcement a first-class operation of the sandbox host without depending on a heavier policy engine. Callers that need budget tracking or per-capability payload gating construct a stricter checker and inject it through `createVmSandbox({ checker })`. ## Features - Glob-compiled allowlists for `permissions.fs[]` (path globs with `*`, `**`, `?`) and `permissions.fetch[]` (exact host or `*.suffix` pattern). - Path normalisation rejects `..`-traversal before matching so `"/srv/data/../../etc/passwd"` is matched as `"/etc/passwd"`, not fooled into `"/srv/data/passwd"`. - Returns frozen `PermissionDecision` objects so the proxy hot path never accidentally mutates the policy answer. - All decisions are synchronous; the policy answer never performs IO. ## Usage: Basic ```ts import { createPermissionChecker } from "@suaveplan/api-sandbox-vm/permissions/checker"; const checker = createPermissionChecker({ policy: { fs: ["/srv/data/**"], fetch: ["api.example.com", "*.cdn.example.com"], }, }); checker.checkFs("/srv/data/users.json", "read").allow; // true checker.checkFetch("https://api.example.com/v1/users").allow; // true checker.checkFetch("https://evil.example.org/leak").allow; // false ``` ## Usage: Advanced Compose with a host-supplied audit sink so every denial is observable: ```ts import { createPermissionChecker } from "@suaveplan/api-sandbox-vm/permissions/checker"; import type { PermissionChecker } from "@suaveplan/api-sandbox-vm/permissions/checker"; function withAudit( inner: PermissionChecker, audit: (capability: string, reason: string) => void ): PermissionChecker { return { checkFs(path, mode) { const d = inner.checkFs(path, mode); if (!d.allow) audit(`fs.${mode}`, d.reason); return d; }, checkFetch(url) { const d = inner.checkFetch(url); if (!d.allow) audit("fetch", d.reason); return d; }, checkCapability(id, payload) { const d = inner.checkCapability(id, payload); if (!d.allow) audit(id, d.reason); return d; }, }; } ``` ## API Reference - `createPermissionChecker(options)` — build a checker from a `PermissionsManifest`. `options.policy` is the manifest block exactly as declared in `plugin.json`. - `PermissionChecker` — structural type with synchronous `checkFs`, `checkFetch`, `checkCapability` methods. - `PermissionDecision` — discriminated union of `{ allow: true }` or `{ allow: false; reason: string }`. ## Implementation Notes The glob compiler escapes regex meta-characters first, then performs the `**` → `.*`, `*` → `[^/]*`, `?` → `[^/]` substitution in three passes via a sentinel token to keep the ordering safe (replacing `**` to `.*` before `*` to `[^/]*` would otherwise mis-translate `**`). Host patterns use a string-suffix match rather than a regex to keep the hot path branch-predictable. The `checkCapability` default is permissive precisely because the full payload-shape contract is owned by `api-sandbox-permissions`; hosts SHOULD compose a stricter checker once that package ships. The in-package fallback still rejects an empty capability id so that an obviously-malformed call fails closed rather than silently succeeding.