terok-sandbox¶
The hardened-Podman runtime that powers terok.
terok-sandbox launches per-task containers with a credential vault, a gated git server, and egress firewall.
What it provides¶
- Hardened container lifecycle — rootless Podman containers launched through a
Sandboxfacade, services are provided by a supervisor whose lifecycle is managed by the container's OCI hooks - Credential vault — long-lived secrets stay on the host, SQLCipher-encrypted at rest. The container receives short-lived phantom tokens that are exchanged for the real value at the moment of use, scoped per route, audited per request.
- Per-task git gate — a token-authenticated HTTP mirror of the upstream repository. Tasks clone and push only through the gate; the operator chooses whether the gate forwards to upstream automatically or only on human review.
- Shield install + drive — a thin adapter that installs the terok-shield OCI hooks at setup time and drives the firewall at runtime (allow / deny / up / down).
- Clearance composed in-supervisor — every container gets a
per-container supervisor that hosts the hub, verdict server, and
desktop notifier in one short-lived process, spawned by an OCI
hook and reaped by
podman wait. No lingering daemons between tasks;pgrep terokis empty when no containers are running. - Setup as one call —
sandbox_setup()brings the whole stack up idempotently;sandbox_uninstall()undoes it.
Per-container supervisor¶
The supervisor composes terok-vault, clearance-hub, verdict, and
notifier into a single in-process composition per container.
Lifecycle, end-to-end:
terok-sandbox preparewrites a sidecar config to$XDG_STATE_HOME/terok/sidecar/<container-name>.jsonand emits the podman flags for the container (vault socket bind-mount, shield annotations, gate token, …).- The operator (or the calling orchestrator) runs
podman run. - The OCI prestart hook installed by
terok-sandbox setupreads the sidecar, thenPopens a stdlib-only wrapper that supervisesterok-sandbox supervisor <id>— the actual long-running asyncio loop. The wrapper restarts the supervisor up to five times on non-zero exit, with exponential backoff capped at 60 s. - The supervisor brings up a
VerdictServer,ClearanceHub,VaultProxy(insocketortcpmode per the sidecar), and a desktop notifier subscriber; awaitspodman wait <id>; tears them down in reverse order on container exit. - The OCI poststop hook SIGTERMs the wrapper PID (recorded under
$XDG_STATE_HOME/terok/pids/supervisor-<id>.pid) and unlinks the PID file.
Operators don't usually invoke terok-sandbox supervisor directly —
it's hidden from the main help and spawned by the hook chain — but
running it under a debugger after terok-sandbox prepare is a
supported way to step through a faulty composition without involving
podman.
Where it sits in the stack¶
terok-sandbox is the boundary layer. Above it, single-task callers (terok-executor) and multi-task orchestrators (terok) treat the sandbox as a black-box "give me a hardened container." Below it, it composes terok-shield for egress filtering and terok-clearance for the operator-in-the-loop verdict path.
The split exists so that callers do not need to understand nftables, OCI hook wiring, vault sockets, or systemd unit lifecycles to get a safe container.
Installation¶
For most users this dependency is pulled in transitively by
terok-executor or terok. Install it directly only when building
a custom orchestrator on top of the sandbox API.
Quick start¶
from pathlib import Path
from terok_sandbox import RunSpec, Sandbox, SandboxConfig
sandbox = Sandbox(SandboxConfig())
sandbox.run(
RunSpec(
container_name="task-001",
image="terok-l1-cli:ubuntu-24.04",
env={},
volumes=(),
command=(),
task_dir=Path("/var/lib/myapp/task-001"),
)
)
See the developer guide for the full lifecycle and integration patterns.