3.4 KiB
fs proxy
(no submodules)
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
readFileandwriteFilewith policy decisions computed synchronously and the host call deferred until allowance is confirmed. - Denials raise
SAND_VM.PERMISSION_DENIEDimmediately (Promise.reject) and increment theplugin.permission_denied_totalcounter with{ plugin, capability: "fs", mode }attributes — identical labels to the metric emitted byapi-sandbox-permissionsso the host's dashboard aggregates across implementations. - Frozen receiver: the returned object is
Object.freezed and the adapter reference is not exposed.
Usage: Basic
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:
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.pluginIdare all required.interface FsProxy— frozen receiver withreadFile,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.