Runtime Sandbox
Since v2026.4.16, tool subprocesses run under kernel-level isolation, outside the initrunner process. This is distinct from the PEP 578 audit-hook sandbox (security.tools.audit_hooks_enabled), which runs inside the Python interpreter.
Pick one of three backends:
- Bubblewrap — Linux user namespaces. No daemon, no Docker, no root. Default on Linux.
- Docker Sandbox — Containers via the Docker daemon. Works on macOS, Windows, and Linux. Supports pinned images and bridge networking.
- SSH Backend — Remote execution on an existing host via OpenSSH (since v2026.5.1). Not a kernel sandbox; use it to choose where code runs, not for containing untrusted code.
backend: auto tries bwrap on Linux and falls back to Docker. SSH is opt-in only (backend: ssh) because it needs an explicit remote host. This page is the shared config reference; the linked pages cover each backend's details, examples, and limits.
Configuration
security:
sandbox:
backend: auto # auto | bwrap | docker | ssh | none (default: none)
network: none # none | bridge | host
allowed_read_paths: []
allowed_write_paths: []
memory_limit: "256m"
cpu_limit: 1.0
read_only_rootfs: true
bind_mounts: []
env_passthrough: []
docker:
image: "python:3.12-slim"
user: "auto"
runtime: null # null (runc) | runsc | kata-runtime | kata-qemu | kata-fc | kata-clh
extra_args: []
ssh:
host: my-build-box # required when backend: ssh
remote_cwd: /srv/work # optional; SSH login dir if unset
identity_file: null # optional override; falls back to ~/.ssh/config
config_file: null # optional override
connect_timeout: 10
control_persist: "60s"backend
| Value | Behavior |
|---|---|
none | No isolation. Tool subprocesses run on the host. |
bwrap | Bubblewrap (Linux only). Lightweight user-namespace sandbox. |
docker | Docker container. Requires a running Docker daemon. |
ssh | Remote execution on a host via OpenSSH. Not a kernel sandbox. See SSH Backend. |
auto | Prefers bwrap on Linux, falls back to Docker. Never selects ssh (requires explicit host) and never falls to none. |
network
| Value | bwrap | docker |
|---|---|---|
none | --unshare-net (empty namespace) | --network none |
bridge | Not supported (raises error) | --network bridge |
host | Host network (no namespace) | --network host |
docker sub-config
image— Docker image (defaultpython:3.12-slim).user— Container user."auto"maps to the currentuid:gidwhen writable mounts exist.nullruns as root.runtime— Container runtime.null(default) uses Docker's default (runc). Other values:runsc(gVisor),kata-runtime,kata-qemu,kata-fc,kata-clh(Kata microVM hypervisor variants). Validated at preflight against the runtimes Docker has registered; an unregistered choice fails with a per-runtime install hint. Since v2026.5.2.extra_args— Additionaldocker runflags. The schema blocks dangerous flags such as--privilegedand--cap-add.--runtimeis rejected here too; useruntimeabove instead.
ssh sub-config
host— required. Alias from~/.ssh/configoruser@hostname.remote_cwd— working directory on the remote host. Defaults to the SSH login directory.identity_file— optionalIdentityFileoverride. Prefer setting this in~/.ssh/config.config_file— optional~/.ssh/configpath override.connect_timeout— seconds for the initial connection (default10).control_persist— how long the multiplexed connection stays warm (default"60s", any OpenSSH duration string).
See SSH Backend for the full reference, the v1 limits (no bind_mounts, no python_exec yet), and the security posture warning.
For when to pick which backend, and what each runtime buys you, see Sandbox Comparison.
Backends at a glance
| bubblewrap | Docker | SSH | |
|---|---|---|---|
| Platform | Linux only | macOS, Windows, Linux | Anywhere ssh is on PATH |
| Daemon | None (bwrap binary) | Docker daemon required | OpenSSH client; remote sshd |
| Isolation | User namespaces | Container | None (host-level on remote) |
| Startup cost | fork+execve | ~200–500ms per call | ~150–500ms cold, tens of ms warm (ControlMaster) |
| Filesystem | Host /usr, /bin, /lib bound read-only | Pinned image | Whatever the remote host has |
| Network isolation | --unshare-net | --network none | Not enforced |
| Bridge networking | Not supported | --network bridge | Rejected at config load |
| Resource limits | systemd-run --user (skipped with a warning if unavailable) | -m, --cpus, --pids-limit | None in v1 |
| Bind mounts | Supported | Supported | Rejected at config load (v1) |
| Install | apt install bubblewrap | apt install docker.io or Docker Desktop | apt install openssh-client |
Mount validation
Mounts fall into two categories:
- User-configured (
allowed_read_paths,allowed_write_paths,bind_mountsfrom role YAML) — validated at load time against the role's permitted roots. A typo'd/etcmount fails before the role ever runs. - Tool-internal (e.g.
python_execwrites code to/tmpand mounts it at/work/_run.py) — code-controlled, trusted, no validation.
Audit
Every sandboxed call logs a sandbox.exec security event:
| Field | Meaning |
|---|---|
backend | which backend ran the command |
argv0 | the command that ran |
rc | exit code |
duration_ms | wall-clock time |
Query with:
initrunner audit security-events --event-type sandbox.execAdaptive preflight
backend: auto and backend: bwrap run a functional probe before launching any tool:
bwrap --ro-bind /usr /usr -- /bin/trueIf the probe fails, SandboxUnavailableError.remediation reads /proc/sys/kernel/unprivileged_userns_clone and /proc/sys/kernel/apparmor_restrict_unprivileged_userns and emits the specific fix — sysctl, AppArmor profile reinstall, or switch to backend: docker. The CLI renders the error as a clean Rich panel and exits with code 1.
initrunner doctor --role <file> now renders a sandbox row showing the resolved backend and readiness — Docker daemon reachable, bwrap probe passing, image pulled.
Migrating from security.docker
security.docker has been removed. A role still using it fails schema validation with migration instructions:
# Old (removed in v2026.4.16)
security:
docker:
enabled: true
image: python:3.12-slim
network: none
# New
security:
sandbox:
backend: docker
network: none
docker:
image: python:3.12-slimBundle metadata
Published bundles declare supported_sandbox_backends in the manifest. initrunner install checks the host and warns when none of the listed backends is available. See OCI Distribution for the full manifest schema.