first commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# fs proxy
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`installFsProxy` returns the sandbox-side filesystem surface that
|
||||
plugins consume. Every method gates the call through the supplied
|
||||
`PermissionChecker` before delegating to the opaque host adapter, so a
|
||||
denied call never reaches the host's `fs/promises` layer. The proxy is
|
||||
closure-bound: the adapter reference is reachable only through the
|
||||
returned methods, so the sandbox cannot retrieve the adapter from the
|
||||
proxy receiver and bypass the permission gate.
|
||||
|
||||
This module owns the sandbox-side half of the `fs` capability surface;
|
||||
the adapter on the other side of the closure is supplied by `api-host`
|
||||
and performs the real `node:fs/promises` calls after the policy decision
|
||||
has been confirmed.
|
||||
|
||||
## Features
|
||||
|
||||
- Permission-checked `readFile` and `writeFile` with policy decisions
|
||||
computed synchronously and the host call deferred until allowance is
|
||||
confirmed.
|
||||
- Denials raise `SAND_VM.PERMISSION_DENIED` immediately
|
||||
(`Promise.reject`) and increment the
|
||||
`plugin.permission_denied_total` counter with
|
||||
`{ plugin, capability: "fs", mode }` attributes — identical labels
|
||||
to the metric emitted by `api-sandbox-permissions` so the host's
|
||||
dashboard aggregates across implementations.
|
||||
- Frozen receiver: the returned object is `Object.freeze`d and the
|
||||
adapter reference is not exposed.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { installFsProxy } from "@suaveplan/api-sandbox-vm/proxies/fs-proxy/fs-proxy";
|
||||
import { createPermissionChecker } from "@suaveplan/api-sandbox-vm/permissions/checker";
|
||||
|
||||
const checker = createPermissionChecker({
|
||||
policy: { fs: ["/srv/data/**"] },
|
||||
});
|
||||
const fs = installFsProxy({
|
||||
checker,
|
||||
pluginId: "demo",
|
||||
adapter: {
|
||||
readFile: (p) => realReadFile(p),
|
||||
writeFile: (p, d) => realWriteFile(p, d),
|
||||
},
|
||||
});
|
||||
|
||||
await fs.readFile("/srv/data/users.json");
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Wrap the adapter to record host-side latency without polluting the
|
||||
proxy contract:
|
||||
|
||||
```ts
|
||||
import { installFsProxy } from "@suaveplan/api-sandbox-vm/proxies/fs-proxy/fs-proxy";
|
||||
import { hrtime } from "node:process";
|
||||
|
||||
const adapter = {
|
||||
readFile: async (p: string) => {
|
||||
const start = hrtime.bigint();
|
||||
try {
|
||||
return await realReadFile(p);
|
||||
} finally {
|
||||
const elapsed = Number(hrtime.bigint() - start) / 1e6;
|
||||
telemetry.recordFsLatency("read", elapsed);
|
||||
}
|
||||
},
|
||||
writeFile: (p, d) => realWriteFile(p, d),
|
||||
};
|
||||
const fs = installFsProxy({ checker, adapter, pluginId: "demo" });
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `installFsProxy(options): FsProxy` — build the proxy.
|
||||
`options.checker`, `options.adapter`, `options.pluginId` are all
|
||||
required.
|
||||
- `interface FsProxy` — frozen receiver with `readFile`, `writeFile`.
|
||||
- `interface FsHostAdapter` — the host-supplied counterpart that
|
||||
actually performs the IO.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The proxy is the only place where sandbox-side `fs` calls cross the
|
||||
permission boundary. The adapter MUST never be exposed back to the
|
||||
sandbox; the closure-binding guarantees that property at the language
|
||||
level. Denials are accounted for with the same metric name as the
|
||||
sister `api-sandbox-permissions` package emits, so dashboards see one
|
||||
denial-rate series rather than two. The `mode` attribute on the
|
||||
counter is `"read"` or `"write"` — keeping it on the metric (rather
|
||||
than in a separate counter) means cardinality remains bounded to two
|
||||
values regardless of the path being requested.
|
||||
Reference in New Issue
Block a user