Advanced: the egress door inside the confinement boundary
This section is for a reviewer auditing the confinement itself.
This section is for a reviewer auditing the confinement itself. Section 8 describes the boundary a run gets; section 11 states that egress is unrestricted for a run that does not opt in, and names the proxy as the answer for one that does. What follows is the piece of that answer that lives in the boundary: the socket the proxy is reached through, and what had to be true of the boundary for such a socket to exist at all. It is written as a set of prices paid. Everything the door does not yet contain is in Egress §8 and §9, and this section does not soften any of it.
11A.1 What is in the boundary now
A run may declare an egress policy. When it does, the engine mints that run its own unix socket, in a directory it owns, outside every tree the confinement makes writable, and makes exactly that one path reachable from inside the boundary. If any part of that cannot be done, the run does not start. There is no configuration under which a run declares an egress policy and then runs with no door.
Read that last sentence exactly as narrow as it is, because an earlier draft of this document overstated it. It said "…and then runs with an unmanaged network", which contradicts §11A.6 four paragraphs below — and §11A.6 is the true one. What is guaranteed is that a declared policy either gets its door or gets a refusal. What is not guaranteed, and is not claimed anywhere in this release, is that traffic goes through that door: neither local arm creates a network namespace, so a run may ignore the socket and open its own connection. The run's own record says so in its own words — it reports a door as REACHABLE, never as enforced.
That is a change to what section 8's boundary contains — the first one that adds a reachable resource rather than a writable tree — and it is why the paragraph below about a widened guard belongs in this document rather than only in Egress.
11A.2 The placement, and the two alternatives that were rejected on measurements
A unix socket's path lives in a fixed C array of 103 usable bytes across both platforms
this product confines on. Every short writable path on macOS is under the temp root; and on
Linux the boundary hands the run a private /tmp. Those two facts do not both fit, so the
placement had to be decided rather than defaulted:
- A different path per platform was rejected. It buys compatibility by splitting the one property the control exists to give — one policy on two systems. A boundary whose reviewer has to audit two arrangements is two boundaries.
- Removing the private
/tmpwas rejected on a measurement, and the first framing of it was wrong. The Linux runner's/tmpis an ext2/ext3 filesystem, not a tmpfs; a file created on the host's/tmpis nonetheless invisible inside the confined run and visible without the flag. ⇒ The flag is mount isolation, not filesystem type. Removing it would show every run the host's/tmpand each other's files — a channel between concurrent runs. That is a worse defect than the one it would fix. - A directory the proxy owns, outside both temp roots, was ratified. The price is one named exemption in a guard this product wrote itself (§11A.4), and that price is stated rather than absorbed.
11A.3 Why the socket is not in the run's own directory
Section 8's boundary binds the run directory writable — it must, because the transcript
and the captures land there. An agent that can write it could unlink the socket and bind its
own, and every subsequent line of the audit record would describe a proxy the agent supplied.
A socket the agent can replace is a name, not an identity. So the door is minted outside every
writable tree, the agent may connect to it and may do nothing else to it, and on both
platforms it cannot delete it — on Linux because the socket is a mount point (EBUSY), on
macOS because it is granted for reaching and reading and never for writing (seatbelt checks
unlink against the target path, so a write grant would hand the power straight back).
11A.4 The container guard, widened by exactly one path
Section 8's container arm refuses any container argv that bind-mounts a unix socket, because a
process holding a runtime's control socket can start a second container with any flags at all.
The egress door is a unix socket and is the opposite object — the only way out of a
--network none container, with everything through it judged.
The guard therefore has one exemption, under four conditions, all required: a door was
minted for this run; the mount source is that door — the same filesystem object, or failing
that the same path string; the path named in the exemption is under this engine's own egress
root; and the path is not a container runtime's control socket, tested the same two ways every
other source is. It widens the "this source is a .sock" test and nothing else — privileged mode,
host networking, added capabilities, disabled seccomp or AppArmor, the host user namespace, the
host root filesystem and the runtime sockets are judged exactly as before. The same change made
the guard stricter in one place: everything under the engine's egress root is refused except
this run's own door — the root itself, any path that contains it, another run's socket
directory, another run's socket, any descendant at any depth, and each of those written with a
trailing slash, with . or //, through .., or reached through a symlink.
⚠ Two sentences in the paragraph above are corrections, and both were measured by review rather than noticed. (1) The conditions were described as "unchanged in number and in meaning" across a round in which two of the four moved in meaning: the equality became an equality "in some spelling" and then an identity comparison, and condition 3 changed its subject from the mount source to the exemption. The number is genuinely unchanged; the meaning was not. (2) Condition 3 was described as the condition that stops a mis-composed run naming something else — which its new subject does not do: a caller threading an unresolved symlinked door whose target is outside the root gets that outside directory admitted. That does not open in the shipped composition, which resolves the door before handing it over, and it is not closed today.
**AND THE "every spelling" CLAIM WAS FALSE ON macOS ** The rung compared path
TEXT, and on a case-insensitive filesystem — the macOS default — <EGRESS-ROOT>/<other-run-id>
and <EGRESS-ROOT> are the same directories as their lower-case spellings. Both were
admitted: the same hole this section says was closed, reachable through one flipped letter, on
the platform whose run separation §11A.5 already marks ARGUED rather than STRUCTURAL. The repair
is not a case-insensitive string comparison — this filesystem also ignores Unicode
normalisation, so that would have fallen to the next spelling. The guard now asks the filesystem
which object a path names (device and inode) for the source, for each of its ancestors and for
the root, so every encoding the kernel resolves to one inode collapses before any comparison. The
whole 39-row population is re-run against 130 generated case twins, and the property asserted is
that an admission is legal only when the source is the same object as the exempted door.
Identity has a precondition, and the precondition is now established rather than assumed.
The kernel cannot be asked which object a missing directory is, so with the egress root absent
the rung falls back to a case-folded text comparison — which a Unicode-variant spelling walks
past. That fallback was reachable in production Measured through the production path in both
directions: root absent ⇒ a twin spelling of the root is ADMITTED; root present ⇒ REFUSED. The
container composer now creates the root before it asks — one idempotent MkdirAll on this
engine's own state home — so the fallback is unreachable from any composer this engine has. This
was a documented limit; it is now a removed one, which is the stronger outcome and the reason the
sentence declaring it is gone from Egress rather than softened.
An earlier draft of this paragraph said only "mounting the egress directory is now
refused by name", and a security review measured that as false for a per-run directory. The
guard of that draft refused the root and everything containing it and admitted everything
under it — so <egress-root>/<another-run-id>, which is precisely the object the per-run
door exists to keep private, passed. A container given it holds another concurrent run's live
door, and that run's proxy would then judge the container's traffic under the victim's
policy. The repair refuses the whole neighbourhood in both directions and composes the
exemption with the descendant test rather than after it.
Ask for the control, not the claim. The exemption is proved narrow by a table that drives
the real guard over 35 hazardous shapes and requires a refusal for every one: the root, its
parent, the host root, another run's directory and socket, a deeper descendant, a descendant
that does not exist yet, a directory whose name ends .sock, this run's own socket
directory, the .././///trailing-slash spellings of those, four symlinks whose targets are
members of the set, a path one byte off the door, a path that merely begins with the door, the
Docker socket, the Podman socket, a rootless Podman socket directory and an unrelated .sock.
Exactly four rows are admitted, and all four are spellings of one path — this run's own
door, resolved and unresolved — because a table where every row expects a refusal would pass
against a guard that refuses the engine's own door too.
⚠ One member of that population is a named limit rather than a guarded shape: a
relative mount source (source=run-B/d.sock). Every runtime this engine drives reads a
relative source as a named volume and not as a host path — Docker refuses a relative bind
source outright — so it reaches no directory under the egress root. That behaviour is asserted
by a test so a future change to it is visible, but it is declared scope, not a proof about
every runtime.
11A.5 Separation between concurrent runs, and the one check that is owed
Two runs on one host run as the same operating-system user, so file modes do not separate them and are not claimed to. The separation is the confinement's:
- Linux — STRUCTURAL. The egress root is replaced by an empty tmpfs in each run's mount namespace before that run's own socket is bound back over it. Another run's socket does not exist in this run's view. There is no rule to evaluate and nothing to get wrong at evaluation time: the path is absent.
- macOS — ARGUED, and weaker than the line above. The egress root is denied for
network-outboundandfile-read*, and exactly one literal path is allowed back, in that order, because seatbelt is last-match-wins. This is a RULE, not an absence — the run can still name the other socket, and the separation depends on the kernel evaluating that rule the way the profile intends.
⚠ The macOS clause is ARGUED. Read the three parts separately, because they are not equally strong, and the difference is the point of this block:
- Last-match-wins IS proven on this product, and it is not an assumption. The existing
darwin confinement already depends on it: the profile emits a blanket
(deny file-write*)and then a later(allow file-write* (subpath <workdir>)), and a test drives a realsandbox-execand fails if the workdir write is refused. Under first-match-wins no confined macOS run could write its own workdir. The ordering semantics are live-kernel-verified. - What is NOT measured is the
network-outboundhalf, and it is the load-bearing half: that seatbelt evaluatesconnect(2)on anAF_UNIXpath asnetwork-outboundwith a path filter, and that(deny network-outbound (subpath <dir>))blocks aconnect(2)to a socket beneath that directory. The(allow network-outbound (literal <path>))idiom is real and is what Apple's own profiles use, but this product has not measured it against a kernel. - The measurement that would settle it, and why it cannot be taken here. It is a live
sandbox-execrun of the composed profile, with one process bound to a socket under the root and a second attemptingconnect(2)to it — expectingEPERMfor the other run's path and success for its own literal. macOS refuses to apply a sandbox profile inside a sandbox (sandbox-exec: sandbox_apply: Operation not permitted, exit 71 — measured, three occurrences, both by the engineering leg and independently by its reviewer), and every leg that touches this code runs inside one. ⛔ Disabling that confinement to obtain the number is not an acceptable way to get it. The measurement is owed from an unconfined macOS host.
⇒ A reviewer asking "is this enforced on macOS?" must be told: the profile is composed
correctly under a rule this product already relies on and live-tests elsewhere; whether the
kernel applies that rule to an AF_UNIX connect is asserted and owed. Nothing equivalent is
outstanding on Linux.
11A.6 What this does not change about section 11
- the fleet's own Go toolchain does not use the door (Go's HTTP client does not read
ALL_PROXY, and cannot parse a unix socket given inHTTPS_PROXY— it silently dials[::1]:1080instead, so the engine deliberately does not set that variable); pipon a stock macOS install cannot speak SOCKS5 at all (a missing Python package, not the socket form — identical over a TCP listener);- the remote (ssh) lane has a far door of its own on one shape of far host, and refuses by
name on the others. ⚠ A far
spawn declaring a policy is governed where the far host's rows measure
seatbeltandseatbelt-network-denyand this engine jails that backend on that platform, and refused by name on alandlockhost, on an unmeasured row, and on a backend outside this engine's jail scope for that platform (§9B.3). ⇒ Coverage on this lane is therefore per far host and measured, never fleet-wide, and a refusal is a refusal by name rather than an ungoverned dispatch.