Initial Commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# `checker/checker` — `PermissionChecker`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module is the composite entry point that the two sandbox hosts
|
||||
(`api-sandbox-vm`, `api-sandbox-wasm`) wire into their worker capability
|
||||
proxy. It binds a `PermissionPolicy` to a `PermissionInstruments` handle
|
||||
and exposes the three production check methods plus three `assert*`
|
||||
helpers that throw `SandboxPermissionError` in the default `"enforce"`
|
||||
deny mode.
|
||||
|
||||
Construction is per plugin-worker pair — never global — because the
|
||||
policy and instruments both pin the `plugin` label and the worker
|
||||
boundary is the natural granularity for the check + telemetry pair.
|
||||
|
||||
## Features
|
||||
|
||||
- `PermissionChecker.checkFs / checkFetch / checkCapability` — invoke
|
||||
the underlying pure function AND record the deny counter on a deny
|
||||
outcome.
|
||||
- `PermissionChecker.assertFs / assertFetch / assertCapability` — same
|
||||
as above, but in `"enforce"` mode raise `SandboxPermissionError`
|
||||
on deny; in `"warn"` mode return the deny result silently after
|
||||
recording the counter.
|
||||
- `DenyMode` — `"enforce"` (default) or `"warn"`.
|
||||
- Deny telemetry — every deny path records exactly one
|
||||
`plugin.permission_denied_total` increment with the documented
|
||||
`{ plugin, capability, reason }` label set.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { PermissionChecker } from "@suaveplan/api-sandbox-permissions/checker";
|
||||
import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments";
|
||||
import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
|
||||
|
||||
const checker = new PermissionChecker({
|
||||
policy: new PermissionPolicy({
|
||||
permissions: { fs: ["./uploads/*"], fetch: ["api.stripe.com"] },
|
||||
}),
|
||||
instruments: createPermissionInstruments({ plugin: "billing" }),
|
||||
});
|
||||
|
||||
const fs = checker.checkFs("./uploads/avatar.png", "read");
|
||||
console.assert(fs.outcome === "allow");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { PermissionChecker } from "@suaveplan/api-sandbox-permissions/checker";
|
||||
import { createPermissionInstruments } from "@suaveplan/api-sandbox-permissions/instruments";
|
||||
import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
|
||||
import { SAND_PERM_CODES, SandboxPermissionError } from "@suaveplan/api-sandbox-permissions/error-codes";
|
||||
|
||||
// Canary mode — record denies on metrics, do not block the call.
|
||||
const canary = new PermissionChecker({
|
||||
policy: new PermissionPolicy({
|
||||
permissions: { fs: ["./uploads/*"] },
|
||||
}),
|
||||
instruments: createPermissionInstruments({ plugin: "billing" }),
|
||||
mode: "warn",
|
||||
});
|
||||
|
||||
const warnResult = canary.assertFs("./etc/passwd", "read");
|
||||
console.assert(warnResult.outcome === "deny");
|
||||
// `plugin.permission_denied_total{plugin: "billing", capability: "fs",
|
||||
// reason: "path-not-in-allowlist"}` incremented; no throw.
|
||||
|
||||
// Enforcing mode — throws on deny.
|
||||
const strict = new PermissionChecker({
|
||||
policy: canary.policy,
|
||||
instruments: canary.instruments,
|
||||
// mode: "enforce" — default
|
||||
});
|
||||
try {
|
||||
strict.assertFs("./etc/passwd", "read");
|
||||
} catch (err) {
|
||||
if (err instanceof SandboxPermissionError) {
|
||||
console.assert(err.code === SAND_PERM_CODES.DENIED);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class PermissionChecker`
|
||||
- `constructor({ policy, instruments, mode? })`
|
||||
- `readonly policy: PermissionPolicy`
|
||||
- `readonly instruments: PermissionInstruments`
|
||||
- `readonly mode: DenyMode`
|
||||
- `checkFs(path, mode): CheckResult`
|
||||
- `checkFetch(url): CheckResult`
|
||||
- `checkCapability(payload): CheckResult`
|
||||
- `assertFs(path, mode): CheckResult` (throws in enforce mode)
|
||||
- `assertFetch(url): CheckResult`
|
||||
- `assertCapability(payload): CheckResult`
|
||||
- `type DenyMode = "enforce" | "warn"`
|
||||
- `type CheckerCapabilitySource = "fs" | "fetch" | "db" | "secrets" | "events"`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The class is deliberately stateless apart from its three readonly fields —
|
||||
deny telemetry is delegated to the `PermissionInstruments` handle, which
|
||||
is itself stateless behind the OTel counters. This means the checker is
|
||||
safe to instantiate once per plugin-worker pair and share across every
|
||||
in-flight capability call.
|
||||
|
||||
The `assert*` helpers wrap `check*` rather than the pure functions so
|
||||
the counter increments exactly once per deny: the underlying pure
|
||||
functions do not call `recordDenial`, only the checker methods do.
|
||||
A deny-mode-`warn` call therefore records the same counter the
|
||||
`enforce` mode would, which is what dashboards rely on to detect
|
||||
canary-phase regressions.
|
||||
Reference in New Issue
Block a user