<!-- SPDX-License-Identifier: Apache-2.0 -->

# Sandbox configuration

`config/sandbox.toml` configures how the `exec` tool runs commands. It is optional; the default backend is Bubblewrap.

```toml
backend = "bwrap"
mounts = []

[bwrap]
binaries = ["git", "python3"]
```

## Backends

`backend` selects `bwrap` (default) or `incus`.

- `bwrap` — a fresh Linux namespace sandbox for each command.
- `incus` — one persistent system container per agent.

## Mounts

`mounts` exposes host directories inside the sandbox below `/workspace/`.

```toml
[[mounts]]
source = "/home/user/projects/my-app"
target = "project"
mode = "rw"
```

Mount sources must be absolute paths or begin with `~/`.
Mount targets are relative paths below `/workspace/` and are `ro` or `rw`.
Mount targets must not overlap; configure sibling targets rather than nesting one below another.

## Bubblewrap

`[bwrap] binaries` is an allowlist of host commands made available to `exec`.
Commands containing shell metacharacters or spaces are rejected; arguments are passed separately.

### Filesystem

Each command receives the standard paths:

- `/workspace` — read-write agent workspace.
- `/memories` — read-write agent memories.
- `/blocks` — read-only agent blocks.
- `/skills` — read-write agent skills.
- `/tasks` — read-write agent tasks.
- `/tmp` — fresh 64 MiB temporary filesystem.
- `/proc` and `/dev` — minimal sandbox pseudo-filesystems.

Custom mounts appear below `/workspace/`.
Host account files, certificates, and required system libraries are exposed read-only so configured commands can run.

Commands start in `/workspace` with `HOME=/workspace`.
The environment is rebuilt with a minimal `PATH`, locale settings, values from `workspace/.env`, and configured passthrough variables.

### Devices

Device access is opt-in.

```toml
[devices]
usb = true
all = true
```

`all` exposes the host `/dev` and is substantially broader than `usb`.

### NixOS

On NixOS, the configured binaries and their Nix store requisites are resolved and bound into the sandbox.

## Incus

`backend = "incus"` requires a `[incus]` table.

```toml
backend = "incus"

[incus]
image = "images:fedora/42"
profiles = []
# project = "cireilclaw"
# shutdownOnExit = false
```

- `image` — any image reference accepted by the Incus server.
- `profiles` — optional profiles applied when the instance is created.
- `project` — optional Incus project; defaults to the caller's current project.
- `shutdownOnExit` — whether the runtime stops the agent's instance when the runtime host exits; defaults to `true`.

The instance name is `cireilclaw-{agent-slug}`.
The first command creates and starts the instance; the runtime stops active instances during shutdown unless `shutdownOnExit = false`.
Instances are not destroyed on normal shutdown, so installed packages and container-local state persist.
With `shutdownOnExit = false` the instance keeps running after the host exits, so a later run reuses the running instance instead of stopping and starting it.

Agent directories are mounted into the instance, and the host process UID and GID are mapped into the container so commands run as the host identity rather than container root.
The runtime may use root inside the container during setup to create the mapped account and configure passwordless `sudo`.

The runtime user must be able to access the Incus Unix socket.
Restricted projects must permit `raw.idmap` and the runtime UID and GID.

## Disabling the sandbox

The environment variable `CIREILCLAW_RUNTIME_INSECURE_DISABLE_SANDBOX_I_AM_100_PERCENT_SURE` bypasses Bubblewrap and runs commands directly on the host.
The documented bypass value is exactly `i-am-in-a-container`.
The source also accepts `babe-i-brought-protection` and `we-are-literally-transbians-what` as intentional easter eggs retained for source parity.
All three values are explicit insecure opt-ins; any other value leaves the sandbox in place.
Disabling the sandbox does not make arbitrary commands safe.

## Security model

The sandbox is the least-privilege command boundary between an agent and the host.
The boundary depends on Linux namespaces, bind mounts, the configured binary allowlist, and the security of the host `bwrap` installation and kernel.

`exec` is not the same boundary as conditional file-access rules: once a command is allowed to run, it can inspect, copy, or transform anything visible inside its sandbox.

The runtime's own reads and writes of agent areas and mounts are contained by the kernel.
Each access opens the target with `openat2(2)` relative to the area or mount root and refuses any resolution that leaves it, so a process inside the sandbox cannot redirect the runtime's file access by replacing a path component with a symbolic link between the check and the access.
Symbolic links are followed while they resolve inside the same area or mount; absolute link targets and links that leave it are refused.
Every file tool, attachment download, heartbeat checklist read, skill load, environment-file read, and legacy migration access follows this rule, and the checked boundary is the same object the access acts on rather than a host path string that is resolved again later.
The boundary root is re-verified on each access, so a mount source reachable through another mount cannot be moved and replaced with a symbolic link under the runtime.
Sandbox file access requires kernel support for `openat2(2)` (Linux 5.6 or newer) and reports a platform error elsewhere.

Plugins are not placed inside this sandbox; plugin code is trusted runtime code, and `ctx.paths.resolve` reports the host path a sandbox path maps to without resolving symbolic links.

## Reload

The runtime watches `sandbox.toml` and applies changes live to subsequent commands.
