Finds the process that actually holds a sandbox's network namespace
(005 T060a3, contracts/egress.md).
Why this is a module and not two lines at the call site
pasta -P <pidfile> records pasta's own host-side pid. The tenant runs
in a child. Measured in the isolation container:
pidfile pid = 10 -> ns net:[4026534462] <- the HOST namespace
tenant pid = 11 -> ns net:[4026534599] <- the sandbox namespace
host ns = net:[4026534462]Using the pidfile pid to install the redirect is not an error that surfaces.
nsenter -t 10 -n nft add rule … succeeds: it installs the sandbox's NAT
rule into the host's own namespace. The tenant is left entirely unpoliced,
the host acquires a stray rule that redirects its outbound TCP, and nothing
in the launch reports a problem. Every denial check still passes, because the
tenant reaches nothing it is checked against for unrelated reasons.
That is the single most dangerous mistake available on this path, and it is one identifier long. It gets a module, a name, and this comment.
How the holder is identified
By comparing namespace inodes — /proc/<pid>/ns/net — against our own.
Not by process name, not by tree position, not by assuming the first child.
The inode is the only check a merely-configured-looking namespace cannot fool: two processes are in the same network namespace exactly when the symlinks match. A name-based or position-based check answers a question about process bookkeeping when the question is about kernel identity.
⚠️ A holder whose namespace equals ours is not a holder. find/2 refuses
rather than returning it, because that value's only use is to be handed to
nsenter, and handing it the host namespace is precisely the catastrophe
above. There is no caller for whom "the host namespace" is a useful answer.
Why finding it is a poll rather than a read
pasta forks the tenant after it finishes configuring the namespace. A
single check immediately after launch finds no child at all and reports what
reads as an architectural refusal — "nothing entered a different namespace" —
when the truth is only that nothing has yet. Measured: at t+2s the child did
not exist; it appeared shortly after.
Summary
Functions
The pid of the process inside pasta's namespace, or a refusal.
One pass over pasta_pid's children, without waiting.
Types
Functions
@spec find( pos_integer(), keyword() ) :: {:ok, pos_integer()} | {:error, refusal()}
The pid of the process inside pasta's namespace, or a refusal.
pasta_pid is what the pidfile contained — the host-side process whose
children are searched.
@spec holder(pos_integer(), String.t(), String.t(), keyword()) :: {:ok, pos_integer()} | :none
One pass over pasta_pid's children, without waiting.
Public so the identification rule is testable against a synthetic /proc
on any host — this cannot run outside Linux, and left private the rule that
matters most would be verifiable only where the whole launch works.