Skip to content

Jail confinement

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.

Reuse the spawner’s confinement primitive (don’t build a parallel one)

Section titled “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

TypeHowWhen to use
Persistentjexec <name> into an existing jailOperator-managed jails with preconfigured environments
Ephemeraljail -c command=<binary> auto-destroyedOne-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.

Why both: persistent jails are operator-managed infrastructure (a build jail, 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)

Section titled “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 on the operator image where mdo is configured.
  • helper — a setuid helper binary on deployed hosts (not yet shipped; falls back to 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)

Section titled “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-party binaries — 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

  • mother-hive — the SSH forced-command boundary (a different confinement model)
  • agent-harness — the spawner that jails agents