The OS-level enforcement seam (012 T021, from 005's contracts/hardening.md).
AshSandbox.Resource lets a host declare limits. This behaviour is what
enforces them, and the distinction is the whole point: nothing inside the
BEAM can cap a process's memory. 005 established that the boundary is the
operating system — cgroup v2, namespaces, bubblewrap, sandbox-exec, Job
Objects — and a mechanism that "enforces" limits in Elixir enforces nothing.
Applied before the first instruction, or not at all
apply/2 returns the launch configuration to start the process with, rather
than something to apply to a running process. This ordering is a requirement,
not a convenience:
005R9b measuredtaskpolicy -mbeing silently lost across an intervening exec —taskpolicy -m 100 sandbox-exec … ./hog 300allocates 300 MB under a nominal 100 MB cap and exits 0.014V3 found the Windows analogue: a Job Object cannot be attached before the process's first instruction.
Both fail open, and both pass any check that asks whether the limiting
mechanism was invoked. That is why ExSandbox.Conformance establishes limits
by breaching them rather than by inspecting configuration (FR-012a), and why
a hardening implementation that cannot apply a cap must say so rather than
return a config that silently omits it.
Summary
Callbacks
Builds the launch specification enforcing limits for command.
Releases any OS resources this hardening created for a sandbox — a cgroup directory, a namespace, a temporary profile.
Capabilities this hardening implementation requires of the host.
Types
@type launch_spec() :: %{ cmd: String.t(), args: [String.t()], env: [{String.t(), String.t()}], cd: String.t() | nil }
How to launch a process under these limits.
cmd/args may wrap the requested command (a cgroup runner, bwrap,
sandbox-exec), which is why the caller must launch this rather than the
command it asked about.
@type limits() :: %{ optional(:cpu_millicores) => non_neg_integer(), optional(:memory_mb) => non_neg_integer(), optional(:disk_mb) => non_neg_integer(), optional(:wall_clock_seconds) => non_neg_integer() }
The limits a sandbox is to run under.
Callbacks
@callback apply(command :: {String.t(), [String.t()]}, limits()) :: {:ok, launch_spec()} | {:error, {:cannot_enforce, ExSandbox.Capability.name(), String.t()}} | {:error, term()}
Builds the launch specification enforcing limits for command.
Returns {:error, {:cannot_enforce, capability, detail}} when a requested
limit cannot be enforced on this host. It must not return a spec that
omits the cap — that is the fail-open shape above, and it is indistinguishable
from success at every layer that does not breach the cap to check.
Releases any OS resources this hardening created for a sandbox — a cgroup directory, a namespace, a temporary profile.
Idempotent, for the same reason destroy/1 is.
@callback required_capabilities() :: [ExSandbox.Capability.name()]
Capabilities this hardening implementation requires of the host.
Functions
@spec tier() :: String.t()
What this host can actually enforce, as one sentence fit to be recorded
(029-FR-040).
Why a string, and why it is stored rather than derived on read
FR-040 requires a host that cannot enforce these boundaries either to refuse
to launch or to launch in a reduced tier named in the sandbox's own recorded
state. The reason it must be recorded is the same reason
data_store_placement is: a tier derived at read time describes the host
asking, not the host the sandbox was launched on, and those diverge the
moment a record outlives a redeploy. A reduced tier that is not recorded is
indistinguishable from an enforced one.
⚠️ This is measured, not declared. 029 D18b states the darwin-in-Docker
tier as "no LSM, no seccomp; network layer enforced", and this function is
deliberately not that sentence hardcoded -- a constant would go on reporting
D18b's measurement on a host D18b never saw. What it reads:
/sys/kernel/security/lsm-- the LSMs the kernel has active.capabilityis discounted: every Linux kernel carries it and it is not an MAC layer, so counting it would report "LSM: capability" on a host with no MAC at all. An unreadable path (securityfs unmounted, or not Linux) reads as none, which is the conservative direction: it under-claims enforcement./proc/self/status'sSeccomp:field --0is disabled, anything else is a filter loaded.pastaonPATHand/dev/net/tunpresent -- the two factsExSandbox.Hardening.Linux's network probe needs a device and a binary for, and the two whose absence madepasta --config-netfail outright when it was measured (2026-08-17).
⚠️ The network clause says "enforceable", not "enforced", and the gap is
deliberate. These two facts say the apparatus this host would police egress
with is present; they do not say the sandbox being recorded was launched
through it -- a sandbox with an empty allowlist takes the unpoliced branch.
Writing "enforced" here would be the over-claim that
ExSandbox.Hardening.Linux's private probe_network_policy/0 warns about in its
own comment,
where "we cannot do this" becomes "we demonstrated this".