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

3.4 KiB

fetch proxy

(no submodules)

Purpose

installFetchProxy returns the outbound-HTTP surface visible inside the sandbox. Every request gates through the supplied PermissionChecker before reaching the host adapter, and denied calls never touch the network — they raise SAND_VM.PERMISSION_DENIED and increment plugin.permission_denied_total{capability="fetch"} without opening a socket, performing a DNS lookup, or hitting the host's connection pool.

The proxy intentionally exposes only a request method with a narrow shape (url, optional init) so the sandboxed plugin cannot reach ambient Node primitives like the connection pool, the DNS resolver, the timeout default, or the raw streaming body. Anything richer is the responsibility of an explicit capability (and arrives through installCapabilityProxies).

Features

  • Synchronous permission check; the host adapter call is deferred until allowance is confirmed.
  • Closure-bound adapter — the sandbox cannot pry it back out of the proxy receiver.
  • Denial path records the plugin and capability attributes on the shared plugin.permission_denied_total counter so dashboards see a uniform denial-rate series across permission implementations.
  • URL-parsed host match — the proxy receives the URL as a string and the checker normalises it through new URL(...).hostname; an unparseable URL is rejected by the checker before reaching the host.

Usage: Basic

import { installFetchProxy } from "@suaveplan/api-sandbox-vm/proxies/fetch-proxy/fetch-proxy";
import { createPermissionChecker } from "@suaveplan/api-sandbox-vm/permissions/checker";

const checker = createPermissionChecker({
    policy: { fetch: ["api.example.com"] },
});
const fetch = installFetchProxy({
    checker,
    pluginId: "demo",
    adapter: { request: async (url) => realRequest(url) },
});

const res = await fetch.request("https://api.example.com/v1/users");

Usage: Advanced

Wrap the adapter to enforce a per-tenant header before reaching the upstream:

import { installFetchProxy } from "@suaveplan/api-sandbox-vm/proxies/fetch-proxy/fetch-proxy";

const adapter = {
    request: async (
        url: string,
        init?: { readonly method?: string; readonly body?: string }
    ) => {
        const merged = {
            ...init,
            method: init?.method ?? "GET",
        };
        return realRequest(url, {
            ...merged,
            // host-side adapter is free to attach additional headers
            // that the sandbox cannot see or override.
        });
    },
};
const fetch = installFetchProxy({ checker, adapter, pluginId: "demo" });

API Reference

  • installFetchProxy(options): FetchProxy — build the proxy.
  • interface FetchProxy — frozen receiver with request(url, init?).
  • interface FetchHostAdapter — the host-supplied counterpart that performs the actual outbound HTTP request.

Implementation Notes

The proxy emits the denial metric with a fixed two-attribute label set (plugin, capability) so cardinality stays bounded. The reason from the checker decision is folded into the error context rather than into the metric attribute set so the metric remains aggregatable. Adapter-side errors (network timeouts, host unreachable, TLS failures) bubble up through the proxy unchanged; the proxy only owns the policy boundary, not the transport-level error mapping.