ExSandbox
Isolated execution sandboxes with no host-application concepts.
ex_sandbox is a composition and evidence layer over operating-system
facilities, not a new isolation mechanism. Nothing here invents containment:
cgroup v2, user and mount namespaces, setpriv and bwrap do the confining.
What this library adds is composing them correctly, refusing to run when it
cannot, and producing evidence that the boundary is real.
def deps do
[{:ex_sandbox, "~> 1.3"}]
end
⚠️ Linux is where this library does its job. It installs, compiles and runs
its unit suite on macOS, but ExSandbox.capabilities/0 reports every gating
capability unavailable there and ExSandbox.Mechanism.Beam refuses to
provision — deliberately. ExSandbox.Mechanism.Docker exists for exactly that
host.
Documentation
| Getting started | Provision, start, execute and destroy a real sandbox, end to end. Start here. |
| How to implement a mechanism | The 11 callbacks, in the order to build them, and what each one must not do. |
| How to read a refusal | The three shapes a refusal takes and the decision each one implies. |
| Why refusal is the design | The argument, and the four times this repository shipped the opposite defect. |
What 005-FR-011 means | The citation scheme in the source, and the name Axonn. |
| Provenance | Where this library was extracted from, and what did not come with it. |
| Public interface | The boundary, shipped inside the package and readable at runtime. |
The interface
ExSandbox is the facade: provision/2, start/2, stop/2, destroy/2,
status/2, list_running/1, usage/2, capabilities/0. Each takes a mechanism
module implementing the ExSandbox.Mechanism behaviour.
{:ok, sandbox} = ExSandbox.provision(ExSandbox.Mechanism.Docker, %ExSandbox.Sandbox{...})
{:ok, running} = ExSandbox.start(ExSandbox.Mechanism.Docker, sandbox)
{:ok, :running} = ExSandbox.status(ExSandbox.Mechanism.Docker, running)
{:ok, stopped} = ExSandbox.stop(ExSandbox.Mechanism.Docker, running)
:ok = ExSandbox.destroy(ExSandbox.Mechanism.Docker, stopped)
A module not named in priv/boundary.md is private, whether or not it is
namespaced Internal (012-FR-014). That document ships inside the package
and resolves at runtime through Application.app_dir(:ex_sandbox, "priv/boundary.md"), so a consumer's own test can read the public-interface
table from the installed dependency rather than restating it. The authoritative
list lives in ExSandbox's @moduledoc; if the two disagree, the moduledoc is
right.
Dependencies
:telemetry, and nothing else. The Elixir floor is ~> 1.14 deliberately, so
consumers are not forced onto the platform's version. Both properties are
enforced by dependency_tree_test.exs and boundary_enforcement_test.exs
rather than by convention.
The direction matters more than it looks. Research R2 established that a
wrong-direction reference inside this library compiles cleanly, exits 0,
passes mix deps.tree, and fails only at runtime inside a third-party
consumer's application. --warnings-as-errors is the only build-time check
that catches it, which is why the gate is load-bearing rather than stylistic.
Conformance
ExSandbox.Conformance is the contract's enforcement, usable by any mechanism
implementation:
defmodule MyMechanismTest do
use ExSandbox.Conformance, mechanism: MyMechanism
end
It scores three outcomes, not two: pass, guarantee failure, and capability unavailable — because "this host cannot demonstrate the guarantee" and "this mechanism breached the guarantee" lead to opposite actions.
Tests
mix test # unit + contract; isolation excluded off Linux
docker compose -f docker/compose.isolation.yml up --build \
--abort-on-container-exit --exit-code-from isolation isolation
⚠️ A green mix test on macOS says nothing about whether tenant code is
contained. Six of 005's ten success criteria rest on Linux kernel facilities
with no macOS equivalent, so the :isolation and :reclamation tags are
excluded there — visibly not run, rather than passing vacuously. The container
is a real Linux host with systemd as PID 1 and all five capabilities genuinely
constructed; it has found more than a dozen defects in code that passed
everything locally, including a launch path that failed on every Linux host.
Contributing
⚠️ Read the isolation-harness warning above first. A pull request whose
mix test is green on macOS has verified nothing about containment, and the
container is not optional for any change touching ExSandbox.Hardening.*,
ExSandbox.Egress.* or the mechanisms.
mix precommit is the gate: compile --warnings-as-errors --force,
format --check-formatted, deps.unlock --check-unused,
docs --warnings-as-errors, test.
License
Apache-2.0. See LICENSE.