Skip to main content
A box has a network namespace of its own, and egress on a recipe says what that namespace may reach. By default it is open: the box reaches anything your host can reach outward — but nothing of the host’s own. A service on your host’s loopback is not reachable from the box, and a port the box binds is the box’s alone, so five boxes can each bind :3000 without colliding with each other or with your host. As a scalar egress is open (the default) or none; as a map it routes all of the box’s egress through the dabs proxy engine, which enforces a host allow/deny gate and an optional chain of hooks.
Screens verified by walkthroughs/test_egress.py. The proxy engine runs on bun (host-side); egress: none needs nothing extra. On Linux (the bwrap driver) egress: open is built by pasta — install the passt package and run dabs as an unprivileged user; a boot that cannot have pasta refuses and says so instead of handing the box your host’s network. pasta must be snapshot 2025_05_03 or newer, for the address flags dabs passes: Debian trixie+ and Fedora 41+ package a new enough one, Ubuntu’s and Alpine’s current packages refuse those flags, and there you build from passt.top at the version contrib/recipes/dabseption.Dockerfile pins.

Cut it off

A box with egress: none has no network at all — it cannot even resolve a name:

Gate it by host

An allow list (or deny) is a CONNECT gate: an allowed host reaches, anything else is refused before a byte leaves the box.

Route it through a hook

An http_proxy chain hands the box’s traffic to code you write. tls: terminate opens the encrypted stream, a module: hook sees — and may rewrite — every chunk, and tls: originate re-encrypts it to the real destination.
A hook that discards the upstream body and emits its own line — proof the box’s fetch passed through your code:
This is the mechanism behind a credential broker: the box runs with a fully dummy credential and the real token lives only in a host-side vault. On the way out, the hook expands the dummy in exactly two positions, each named by header/field, place, and host — never by what the bytes look like:
  • the Authorization header of a request, and
  • the refresh_token field of the refresh grant’s JSON body (POST /v1/oauth/token, grant_type=refresh_token).
An optional hosts: list scopes both to particular hosts inside the terminate window. Everything else a request carries is content: a token sitting in message content is passed through un-expanded, a real one is rewritten back to its dummy before the request leaves, and the sighting is recorded host-side to the hook’s alerts: file. Inbound, every real token is rewritten to its dummy and rotation is captured to the vault, so the box only ever holds sentinels.See the confined-claude recipe and contrib/proxy/creds-inject-anthropic/broker.ts in the dabs repo, and the egress field in the recipe schema.