Stable boundaries, disposable workers.
Linux requires namespace-changing calls to happen from a single-threaded process. Ephemeral Sandbox keeps its daemon multithreaded and re-executes the same binary into small, single-purpose process bodies whenever it must create or enter a workspace boundary.
One binary, three runtime roles.
The holder is lifetime, not a worker. Runners are workers, not lifetime. Keeping those jobs separate lets commands come and go while the workspace view remains mounted.
Re-exec topology
The daemon delegates namespace work; it does not join the workspace.
servemultithreaded daemon
session registry + gates
execution registry + watchers
namespace FD ownership
ns-holderOne per workspace. Owns the namespace lifetime and a PID-namespace init child.
ns-runnerOne per operation. Reads one request, enters selected namespaces, writes one result, and exits.
serveDaemon
The multithreaded control process. It creates sessions, captures namespace file descriptors, launches runners, and tracks command completion.
ns-holderPersistent holder
One long-lived process per workspace. It creates and pins the user, mount, PID, and optional network namespaces, then pauses.
ns-runnerOne-shot runner
A fresh single-threaded process for one operation. It joins the required namespaces, performs its payload, returns one result, and exits.
A handshake with real work between the tokens.
Readiness is staged so the daemon never exposes a half-built session. Namespace FDs are captured and the overlay is mounted after ns-up, but before an isolated holder announcesready.
- 01
Spawn holder
Arm the parent-death signal, unshare the namespace stack, map one uid/gid, and fork PID 1.
ns-holder - 02
Signal namespace ready
The holder writes ns-up only after its namespace handles exist.
ns-up - 03
Capture + compose
The daemon opens namespace FDs, optionally installs a veth, and mounts the session overlay through a runner.
/proc/<pid>/ns/* - 04
Finish networking
Isolated sessions receive their interface, address, prefix, and gateway over the control pipe.
net-ready … - 05
Admit work
After ready, the operation layer registers the session and commands may cross its admission gate.
ready
Shared network
The holder skips NEWNETand shares the daemon's network namespace. No veth setup is required.
Isolated network
The holder adds NEWNET; the daemon installs the host veth half, then sends the in-namespace configuration.
Four payloads, one transport.
Each runner receives an EOF-framed JSON request over one pipe and returns one result over another. Namespace file descriptor numbers travel in the request; the selected payload determines which of them the runner enters.
--shellCommand execution
setns order
user → mount → PID → network*
Forks the shell child, applies the child security policy, and waits for the whole process group.
--mount-overlayInitial mount
setns order
user → mount
Creates the merged workspace inside the holder mount namespace and leaves it owned by that namespace.
--file-opSession file operation
setns order
user → mount
Performs the live, fd-relative operation against the mounted workspace without joining its PID namespace.
--remount-overlayLive mount switch
setns order
user → mount
Runs the staged replacement sequence used after a successful LayerStack squash.
The spawn lock is intentionally brief. It covers pipe creation and process spawn so inheritable FDs do not leak between concurrent launches. It does not serialize the operation after the child has started.
Prepare observability before crossing the boundary.
A command is more than a fork. The daemon creates its transcript and terminal plumbing, reserves capacity, launches a runner, and attaches completion state before releasing the session gate. The runner then enters the workspace and applies the final child restrictions immediately before exec.
- 01
Prepare
PTY, transcript, environment
- 02
Reserve
global slot + session ledger
- 03
Launch
runner + best-effort cgroup
- 04
Restrict
setns, caps, seccomp, exec
- 05
Observe
process group, result, cleanup
Terminal contract
stdout and stderr share the PTY stream; stdin is separately backpressured.
Completion contract
The watcher owns process-group status and resolves the caller exactly once.
Cleanup contract
Dropping the admission token removes the ledger entry and may trigger fixed finalization.
Serialize lifecycle edges, not command runtime.
A per-session gate protects admission, completion, file operations, remounts, and destroy decisions. Once a command is attached to the execution registry, that guard is released and the command runs concurrently with other admitted commands.
One command admission window
Lock gate
Ledger insert
Prep + launch
Attach result
Release gate
256
Default live command cap
Configurable and enforced by the command execution registry across sessions.
64
Namespace-operation cap
A separate engine handles mount, remount, and live-session file runners.
1 / session
Lifecycle gate
Serializes correctness edges; it is released before the command wait.
While running
A watcher owns completion, process-group status, and the promise that wakes the caller. Best-effort cgroup placement supports accounting and later remount quiescence.
When complete
Dropping the admission token removes the command from the session ledger. If the ledger drains, an implicit session can publish and destroy according to its fixed finalize policy.
Layered containment, with explicit edges.
Namespace separation defines the workspace boundary. The shell child then adds a restricted environment, capability reduction,no_new_privs, and a seccomp deny table immediately before execution.
- 01
Sandbox container
Outer image, device, cgroup, and host exposure policy.
- 02
Workspace namespaces
User, mount, PID, and optional network separation.
- 03
Overlay + masks
Private writable view with runtime storage hidden.
- 04
Command child
Minimal env, reduced capabilities, no_new_privs, seccomp.
User namespace
Namespace-scoped capabilities with a single uid/gid mapping.
Mount namespace
Private propagation, overlay mount, and masked internal paths.
PID namespace
A private process tree with a session-local PID 1.
Network namespace
Optional netns and veth; shared mode stays in the daemon netns.
Boundary, not a blanket claim
- No per-workspace UTS, IPC, cgroup, or time namespace is created.
- The shell-child seccomp policy is a deny table, not a syscall allowlist.
- Seccomp and capability reduction apply to the shell child—not the holder, runner, or PID 1.
- Isolated networking separates workspace peers; this layer does not provide general destination filtering.
The process tree is the cleanup protocol.
The holder arms its parent-death signal before namespace setup. Its PID-namespace init child arms another. If the daemon dies, the kernel follows that chain without waiting for application cleanup code.
Kernel-enforced kill chain
daemon
control process exits or crashes
ns-holder
parent-death signal fires
PID 1 + session tree
namespace init exits; remaining processes cannot outlive it
Normal destroy
SIGTERM, configurable grace, then SIGKILL.
Mount cleanup
The holder's mount namespace disappears with it.
Next boot
Persisted dead-session records and run directories are reaped.
