Files
skills/docs/api-stack/api/api-sandbox-vm/src/proxies/install-capability-proxies/install-capability-proxies.md
2026-05-29 08:20:56 +01:00

3.3 KiB

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

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:

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<string, unknown>) {
        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.