Browse architecture
Chapter 03

Keep the world alive. Bound the work inside it.

A persistent holder owns each workspace’s Linux namespaces. One-shot runners enter that boundary for commands and runtime operations while narrower restrictions contain child processes.

Section 03 · Namespace runtime & isolation

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.

Process model

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.

serve

multithreaded daemon

session registry + gates

execution registry + watchers

namespace FD ownership

ns-holder
persistent

One per workspace. Owns the namespace lifetime and a PID-namespace init child.

ns-runner
one shot

One per operation. Reads one request, enters selected namespaces, writes one result, and exits.

serve

Daemon

The multithreaded control process. It creates sessions, captures namespace file descriptors, launches runners, and tracks command completion.

ns-holder

Persistent holder

One long-lived process per workspace. It creates and pins the user, mount, PID, and optional network namespaces, then pauses.

ns-runner

One-shot runner

A fresh single-threaded process for one operation. It joins the required namespaces, performs its payload, returns one result, and exits.

Workspace birth

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.

  1. 01

    Spawn holder

    Arm the parent-death signal, unshare the namespace stack, map one uid/gid, and fork PID 1.

    ns-holder
  2. 02

    Signal namespace ready

    The holder writes ns-up only after its namespace handles exist.

    ns-up
  3. 03

    Capture + compose

    The daemon opens namespace FDs, optionally installs a veth, and mounts the session overlay through a runner.

    /proc/<pid>/ns/*
  4. 04

    Finish networking

    Isolated sessions receive their interface, address, prefix, and gateway over the control pipe.

    net-ready …
  5. 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.

Runner protocol

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.

--shell

Command execution

setns order

user → mount → PID → network*

Forks the shell child, applies the child security policy, and waits for the whole process group.

--mount-overlay

Initial mount

setns order

user → mount

Creates the merged workspace inside the holder mount namespace and leaves it owned by that namespace.

--file-op

Session file operation

setns order

user → mount

Performs the live, fd-relative operation against the mounted workspace without joining its PID namespace.

--remount-overlay

Live 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.

Command execution

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.

  1. 01

    Prepare

    PTY, transcript, environment

  2. 02

    Reserve

    global slot + session ledger

  3. 03

    Launch

    runner + best-effort cgroup

  4. 04

    Restrict

    setns, caps, seccomp, exec

  5. 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.

Admission + execution

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

01

Lock gate

02

Ledger insert

03

Prep + launch

04

Attach result

05

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.

Isolation boundary

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.

Defense in depth · outer boundary to child exec
  1. 01

    Sandbox container

    Outer image, device, cgroup, and host exposure policy.

  2. 02

    Workspace namespaces

    User, mount, PID, and optional network separation.

  3. 03

    Overlay + masks

    Private writable view with runtime storage hidden.

  4. 04

    Command child

    Minimal env, reduced capabilities, no_new_privs, seccomp.

User namespace

always

Namespace-scoped capabilities with a single uid/gid mapping.

Mount namespace

always

Private propagation, overlay mount, and masked internal paths.

PID namespace

always

A private process tree with a session-local PID 1.

Network namespace

optional

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.
Ephemeral by construction

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.