Initial Commit
This commit is contained in:
106
packages/api-stack/api/api-sandbox-vm/src/permissions/checker.md
Normal file
106
packages/api-stack/api/api-sandbox-vm/src/permissions/checker.md
Normal 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.
|
||||
Reference in New Issue
Block a user