Whether a running sandbox is policed rather than merely isolated
(005 T060a3/T060a5, contracts/egress.md §Capability probing).
The coincidence this replaces
Hardening.Linux.sandbox_netns_separated?/1 compares namespace inodes. Under
--unshare-net that was enough, because an empty namespace has no route and
so isolation and policy coincided. ExSandbox.Egress.LaunchPlan installs a
working default route, which ends the coincidence: separation is now true for
a namespace that reaches the pool and for one that reaches nothing.
⚠️ Separation alone must therefore stop counting as success. A sandbox in its
own namespace with no registered policy reaches nothing — which passes
every denial check in the conformance suite, because a boundary permitting
nothing is indistinguishable from a correct one under a suite that only tests
denial (contracts/egress.md, "What is wrong today").
Why this takes a map rather than reading /proc itself
The two facts it joins come from different places — the kernel for the
namespace, the Registry for the policy — and neither is reachable from a
macOS host. Taking them as data makes the join testable everywhere, which
matters because the join is the part that was missing, not either fact.
Summary
Functions
:ok only when the sandbox is in its own namespace and a policy is
registered for its /30.
Types
@type observation() :: %{ netns_separated: boolean() | :unknown, source_key: ExSandbox.Egress.Policy.source_key(), registered_allowlist: [ExSandbox.Egress.Policy.destination()] }
@type refusal() :: :no_policy | :not_separated | :unverifiable
Why a sandbox is not confirmed policed.
Functions
@spec policed?(observation()) :: :ok | {:error, refusal()}
:ok only when the sandbox is in its own namespace and a policy is
registered for its /30.
Every other outcome is an error naming which half is missing. There is no outcome meaning "probably fine".