How it works
The door that judges each connection, and the wall that makes the door the only way out, on each kind of machine.
Egress control has two parts, and a run needs both.
- The door judges. Every connection the run opens reaches the door first; the door checks the destination against the run's policy and the hard floor, and either connects it or refuses it.
- The wall makes the door the only way out. Without it, an agent could simply ignore the door and dial the network directly. The wall is enforced by the operating system, so the agent needs no cooperation and cannot opt out.
The three carries
Which wall a run gets depends on where it runs. There are three, and each is chosen automatically.
| carry | used on | how it works |
|---|---|---|
| the network wall | Linux machines, local or reached over ssh | The run is placed in its own network namespace. Its TCP connections on the policy's ports are redirected by the kernel into the door, which reads the original destination and judges it. Name resolution inside the namespace answers only for names the policy lists; other TCP is reset and other UDP rejected. The agent needs no proxy setting. |
| the CONNECT door | macOS machines, local or reached over ssh | The macOS sandbox denies the run all network access except one local port where the door listens. The agent is handed that address as its HTTP(S) proxy, and the door judges each CONNECT. |
| the container bridge | a container run on the local machine, under the saphan-agent harness | The same network wall as on Linux, raised inside the container's own namespace. |
On a machine reached over ssh, the door runs on that machine, beside the run, so the decision is made where the connection would leave from.
What each machine needs
A machine carries the wall only when its record says it can. That record is written by measuring
the machine, never by typing a value: saphan machine probe-confinement performs the act on the host
itself and records the result. See Preparing a machine.
| machine | carried when its record shows |
|---|---|
| local Linux | the netns rung. If the kernel then refuses to make the namespace routable, the run fails before the agent starts, and when AppArmor is the cause it prints the remedy |
| remote Linux | the netns rung and netns-redirect = yes, with landlock confinement |
| macOS | the seatbelt-network-deny rung, with seatbelt confinement |
| local container | netns-redirect = yes on the host, and run.harness = saphan-agent |
⚠ A machine that has never been probed carries nothing. A run placed on it is refused, not run open: an unmeasured machine is treated as a machine that cannot enforce.
Before the run starts
When a run is dispatched, the engine decides which carry the target machine can provide. If none can,
the run is refused before anything is spawned — no model call, no cost — with the marker
EGRESS-CLOSED-DEFAULT-REFUSED and a message that names the lane, what was measured and the fix. See
When something is refused.
Model traffic and the guardrail
When a run is recorded by the guardrail, the guardrail sits between the agent and its model provider. In one configuration the model hosts are then sealed — only the registered guardrail process can reach them, so the agent cannot talk to its model without passing through the guardrail. All of these must hold:
- the run is on a Linux network wall — local, or a remote Linux machine whose measured rung is
netns; - under the
legacyharness and the engine's own jail (not container or vendor confinement); - the run is judged by its backend's baseline; a run dispatched with
--egress-policyis not sealed.
Anything else the run is allowed — a package registry, for example — still goes through the door as usual. Everywhere else the agent is pointed at the guardrail but not forced through it, and the run still gets its full egress policy. When a seal is attempted, the run record says whether it held and, if not, why.
Which runs are not walled
| run | why |
|---|---|
a run dispatched with --egress-open "<reason>" | an explicit, recorded decision to open that one run's network |
a proc run | it has no model and no network wire, so there is nothing to judge |
| a container run whose permission class grants no network | the container already has no network at all |
Every other run is judged.