# Install capability proxies _(no submodules)_ ## Purpose `installCapabilityProxies` wires the host-supplied capability bags into the sandbox's `vm.Context`. The sandbox sees one consolidated `__suaveplan` global containing `fs`, `fetch`, and a `capabilities` map; every method on the map is wrapped so the permission checker is consulted before the host adapter is reached. The receiver and every nested bag are frozen so a sandboxed plugin cannot replace a permission-gated method with an open one. This module is the single source of truth for how capabilities cross the sandbox-host boundary — both the fs/fetch proxies and the wider capability surface enter through this entry point so the permission gate is applied uniformly. ## Features - One `__suaveplan` global with `fs`, `fetch`, `capabilities` — installed as non-writable, non-configurable, enumerable so the sandbox can read it but never replace it. - Permission gating on every capability method: the checker decides before the host adapter is called. - Denials emit `plugin.permission_denied_total{plugin, capability}` with stable label cardinality (no per-method label). - Frozen receivers prevent monkey-patching from inside the sandbox. ## Usage: Basic ```ts import { installCapabilityProxies } from "@suaveplan/api-sandbox-vm/proxies/install-capability-proxies/install-capability-proxies"; import { createContext } from "node:vm"; const context = createContext({}); installCapabilityProxies({ context, checker, fs, fetch, pluginId: "demo", capabilities: { db: { query: async (sql: unknown) => realQuery(String(sql)), }, }, }); ``` ## Usage: Advanced Compose with a host-side audit sink so every denial is reproducible: ```ts import { installCapabilityProxies } from "@suaveplan/api-sandbox-vm/proxies/install-capability-proxies/install-capability-proxies"; const wrapAudit = (checker: PermissionChecker, audit: AuditSink) => ({ ...checker, checkCapability(id: string, p: Record) { const d = checker.checkCapability(id, p); if (!d.allow) audit.record({ capability: id, reason: d.reason }); return d; }, }); installCapabilityProxies({ context, checker: wrapAudit(checker, audit), fs, fetch, capabilities, pluginId: "demo", }); ``` ## API Reference - `installCapabilityProxies(options)` — wire the proxy surface into `options.context`. Returns nothing; side-effects the context. - `interface InstallCapabilityProxiesOptions` — `context`, `checker`, `capabilities`, `fs`, `fetch`, `pluginId`. - `type CapabilityMap` — frozen record of capability id → method bag. - `type CapabilityBag` — frozen record of method name → async fn. ## Implementation Notes The receiver is installed via `Object.defineProperty` with `writable: false, configurable: false` so the sandbox cannot replace or delete `__suaveplan`. Each method wrapper closes over the host function reference; the closure is the only path to the host call, so patching the wrapper from inside the sandbox would not bypass the permission gate (it would only break the path that exists). The `method` argument is not added to the OTel attribute set so cardinality stays bounded; method-level granularity belongs in logs, not metrics.