Jail confinement
Jail confinement
← index
What this is
Colibri can confine spawned agents and external MCP servers inside FreeBSD
jails. The spawner wraps the subprocess command through jexec (persistent
jails) or jail -c (ephemeral jails), so the agent's entire filesystem view
and network are isolated. stdio passes through unchanged — the agent's JSONL
still reaches Glasspane, and the MCP host's stdin/stdout transport still works.
Decisions
Reuse the spawner's confinement primitive (don't build a parallel one)
The agent spawner and the external-MCP host both need to confine untrusted
subprocesses. Instead of building a second confinement layer, the MCP host
reuses the agent spawner's jail_wrap() function directly — the same
JailConfig struct, the same PrivMode policy, the same prepare_spawn_command
pipeline.
Why reuse: two confinement paths → one can drift. The spawner is tested(20+ unit tests in spawner.rs covering named, ephemeral, staged, priv-mode
variants). The MCP host gets a battle-tested implementation for free.
→ COLIBRI-JAILED-AGENT-SPAWN-DESIGN.md,
crates/colibri-daemon/src/spawner.rs
(jail_wrap, JailConfig),
crates/colibri-mcp/src/external.rs
Persistent vs ephemeral jails
| Type | How | When to use |
|---|---|---|
| Persistent | jexec into an existing jail | Operator-managed jails with preconfigured environments |
| Ephemeral | jail -c command= auto-destroyed | One-shot confinement, no state between runs |
The JailConfig struct uses an enum: if name is set, jexec; if path is set,
ephemeral. They're mutually exclusive; name takes precedence.
a worker jail that persists between agent runs). Ephemeral jails are for
untrusted one-shot work — like an external MCP server from a third-party
registry. The caller picks the lifecycle.
→ COLIBRI-JAILED-AGENT-SPAWN-DESIGN.md
Priv-mode policy (mdo on live USB, helper on deployed)
The daemon is unprivileged but jail creation requires root. The priv-mode
policy resolves this without granting the daemon blanket sudo:
mdo— the live USB's operator tool (mdo -u root jail -c ...). Used
mdo is configured.
helper— a setuid helper binary on deployed hosts (not yet shipped;
sudo). The daemon never runs as root.
The policy is configurable via COLIBRI_JAIL_PRIV_MODE and is resolved once
at daemon startup. The same policy applies to agents and MCP servers.
Why not the daemon as root: the daemon spawns arbitrary subprocesses(potentially attacker-controlled, via the MCP registry or task intake).
Running as unprivileged colibri limits the blast radius; the priv-mode
helper grants only the specific operations needed (jail creation).
→ COLIBRI-JAILED-AGENT-SPAWN-DESIGN.md,
crates/colibri-daemon/src/spawner.rs
(PrivMode)
MCP servers are jailed by default (same threat model as agents)
External MCP servers registered in the external MCP registry accept an optional
jail field with the same shape as agent spawn configs. The MCP host applies
the jail wrapper before spawning the server. Servers without a jail field
run on the host (backward compatible).
The MCP host's registry entry supports per-server jail configuration —
different servers can run in different jails. This is a property of the
registry, not a global daemon setting.
Why jailed by default: external MCP servers are arbitrary third-partybinaries — at least as untrusted as the agents Colibri already jails. The
threat model is identical.
→ COLIBRI-EXTERNAL-MCP-PROTOTYPE.md,
crates/colibri-mcp/src/external.rs
See also
- mother-hive — the SSH forced-command boundary (a different confinement model)
- agent-harness — the spawner that jails agents