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

3.3 KiB

checker/fetch — outbound host allowlist

(no submodules)

Purpose

This module owns the permissions.fetch[] enforcement path. Every outbound HTTP call routed through the sandbox host MUST pass through checkFetch before the underlying fetch(...) runs. The matcher is host-only — request bodies, methods, headers, and paths are NOT part of this allowlist. The point is to bound which destinations a plugin can exfiltrate to, not to model an HTTP firewall.

The scheme gate is part of the permission decision: by default only https: is permitted, and the rule must be authored as http:<host> to allow plaintext requests against that host (typically used only for local development against localhost).

Features

  • checkFetch(policy, url) — returns allow / deny.
  • Scheme-aware — https: always passes the protocol gate; http: requires an explicit http: rule prefix.
  • Wildcard suffix — a rule like *.stripe.com matches eu.api.stripe.com but NOT the bare apex stripe.com.
  • URL-parse failure returns deny:invalid-url rather than throwing.

Usage: Basic

import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
import { checkFetch } from "@suaveplan/api-sandbox-permissions/checker";

const policy = new PermissionPolicy({
    permissions: { fetch: ["api.stripe.com", "*.example.com"] },
});

checkFetch(policy, "https://api.stripe.com/v1/charges"); // allow
checkFetch(policy, "https://eu.example.com/api"); // allow
checkFetch(policy, "https://example.com"); // deny: host-not-in-allowlist

Usage: Advanced

import { PermissionPolicy } from "@suaveplan/api-sandbox-permissions/policy";
import { checkFetch } from "@suaveplan/api-sandbox-permissions/checker";
import {
    SAND_PERM_CODES,
    SandboxPermissionError,
} from "@suaveplan/api-sandbox-permissions/error-codes";

const policy = new PermissionPolicy({
    permissions: { fetch: ["http:localhost", "api.stripe.com"] },
});

function gatedFetch(plugin: string, url: string): Response | never {
    const result = checkFetch(policy, url);
    if (result.outcome === "deny") {
        throw new SandboxPermissionError(
            SAND_PERM_CODES.DENIED,
            "fetch capability denied",
            { context: { capability: "fetch", reason: result.reason } }
        );
    }
    // …perform the actual fetch on the host side…
    return new Response();
}

gatedFetch("billing", "http://localhost:8080/dev"); // allowed
gatedFetch("billing", "https://api.stripe.com/v1/charges"); // allowed

API Reference

  • checkFetch(policy: PermissionPolicy, url: string): CheckResult
    • CheckResult = { outcome: "allow" } | { outcome: "deny"; reason: string }
    • reason is one of "invalid-url", "scheme-not-permitted", "host-not-in-allowlist".

Implementation Notes

The host comparator lowercases both sides because the manifest is operator-authored and case may differ from the request URL. The wildcard form *.suffix matches only proper subdomains — the bare apex must be authored as its own exact-match rule.

URL parse failures degrade to a deny rather than a throw so the host worker boundary doesn't need an extra try/catch around the check call. The deny reason "invalid-url" is observable on the plugin.permission_denied_total counter exactly like every other deny reason.