first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
# outbound
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Builds the outbound-socket allowlist from `policy.fetch[]` and wraps the
host's `connect` adapter so denied destinations are refused BEFORE any
OS socket is opened. Denial raises `SAND_WASM_OUTBOUND_DENIED` and
fires the injected `onDeny` hook so the sandbox host can increment
`plugin.permission_denied_total{capability="fetch"}` without coupling
this module to the OTel API.
The matcher supports exact hosts, `*.suffix` wildcards (RFC 6125
semantics — the apex `suaveplan.io` does NOT match `*.suaveplan.io`),
and optional port suffixes. Bare-host rules match any port; port-pinned
rules match only the named port.
## Features
- Exact-host and `*.suffix` rule patterns.
- Port-pinned rules (`api.stripe.com:443`).
- Default destination port 443 when none is supplied.
- O(rules) matching — no regex compilation, suitable for the typical
small allowlists.
- `gateOutboundConnect` wraps a host `connect` adapter; denied calls
never invoke the inner adapter.
## Usage: Basic
```ts
import { buildWasiOutboundAllowlist } from "@suaveplan/api-sandbox-wasm";
const allowlist = buildWasiOutboundAllowlist(policy);
if (!allowlist.isAllowed("api.stripe.com")) { /* deny */ }
```
## Usage: Advanced
```ts
import { gateOutboundConnect } from "@suaveplan/api-sandbox-wasm/outbound";
const gated = gateOutboundConnect(
allowlist,
async (destination) => net.connect(destination),
(destination) => instruments.recordPermissionDenied("fetch", destination)
);
const socket = await gated("api.stripe.com"); // throws SAND_WASM_OUTBOUND_DENIED for non-listed hosts.
```
## API Reference
- `buildWasiOutboundAllowlist(policy): OutboundAllowlist`
- `gateOutboundConnect<TSocket>(allowlist, connect, onDeny)`
- `OutboundAllowlist.{isAllowed(destination), rules}`
## Implementation Notes
Wildcards live inside a single `*.suffix` form rather than full glob
matching because RFC 6125 wildcard semantics are well-defined and
predictable. Adding nested wildcards (e.g. `**.example.com`) would
require careful escape handling that this module does not attempt; if
operators need deeper granularity they should enumerate sub-domains
explicitly.
Out-of-range port suffixes (>65535) on RULES are treated as host-only —
the parser falls back to the bare-host path rather than throwing,
because a parse failure here would be operator-visible during host
boot and add no security value. Out-of-range ports on DESTINATIONS are
treated as the default 443 for the same reason.