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,106 @@
# Permission checker (in-package default)
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.