Skip to content

Security Model

Adi edited this page Oct 19, 2026 · 5 revisions

Explanation. What agentbox defends against, how each layer does it, what's been tested, and what remains.

trust boundaries

Threat model

The executor model, and the code it writes, are untrusted. It reads repositories, issues and dependencies that may contain prompt injection, and it runs whatever it decides to run. It must not be able to:

  • reach the network, or exfiltrate code or secrets directly;
  • read or modify anything on the host beyond its scratch workspace;
  • escalate privileges or reach the Docker daemon;
  • talk to other agents;
  • choose a different model, or spend unbounded compute or money;
  • get code executed outside the sandbox, either through the results it returns or by persuading the planner.

Out of scope: a malicious operator, other local users with Docker access (Docker access is root), and kernel 0-days. For the last, see gVisor below.

Layers

Boundary Enforced by
Agent ↔ network NetworkMode none: only lo. The single way out is the task's gateway socket.
Agent ↔ host recommended: gVisor user-space kernel (runsc) · read-only rootfs · CapDrop ALL · no-new-privileges · default seccomp + AppArmor · uid 64000, which doesn't exist on the host · private IPC · pids/memory/CPU caps · tmpfs /tmp and $HOME · only its workspace, its socket dir and an optional read-only venv are mounted · no Docker socket · the image has no package manager, su/sudo or setuid files. See [[Sandbox specification
Agent ↔ model API Per-task gateway: one-time token, endpoint allowlist, model pinned, per-task token budget. Server-side tools, MCP servers, hosted containers and beta headers are stripped, so no API can fetch, search or connect for the agent.
Agent ↔ other agents One socket per task, mounted only into that task. Socket dirs are 0711 and the state dirs 0700.
agentbox ↔ Docker Only the launcher has docker.sock. Callers choose which task and model to run, never how. Paths are derived from the task id and validated. The command must be claude … (or a check command in a socket-less sandbox). Env allowlist, venv-root allowlist, hard deadline.
Results ↔ host Results are collected through a trusted shadow git dir; nothing in the agent's .git runs. See [[Collecting results safely
Results ↔ planner Report, diff and check output are labelled untrusted data in tool results, MCP instructions and the delegation skill.
Keys ↔ everything Provider API keys live only in llm-bridge (secrets.env). agentbox and the agents never see them.
Your repos ↔ agentbox Nothing is mounted. Each repo operation runs in a short-lived git helper (your uid, no network, no capabilities, read-only root) that mounts only the one folder the planner passed. It refuses paths outside the AGENTBOX_REPOS_ROOT allowlist, folders that aren't a git repo owned by you (so a symlink to /etc gets nothing), and folders that don't exist (it never creates them).
Planner ↔ agentbox MCP on 127.0.0.1 only, with a bearer token on every request and the MCP library's DNS-rebinding guard.

Verified

scripts/smoke-test.sh checks a live agent container:

  • NetworkMode none and only lo;
  • read-only rootfs;
  • CapDrop [ALL] and CapAdd [];
  • no-new-privileges;
  • uid 64000, from both docker inspect and id -u inside;
  • exactly two mounts;
  • the returned branch passes run_checks, inside a sandbox with uid 64000 and only lo.

Also verified:

  • Behavioural probes inside the image: CapEff=0, NoNewPrivs=1, Seccomp=2, DNS and HTTPS fail, writing to /usr fails, no Docker socket, no apk/su/sudo, no setuid files.
  • Git-hook attack: planted hooks, core.hooksPath and core.fsmonitor never ran outside the sandbox.
  • run_checks option injection: a branch named --upload-pack=… is rejected; nothing is created.
  • tests/test_gateway_bridge.py (36 checks): token, endpoint allowlist, model pinning, budget, server-tool and beta stripping, and key injection in llm-bridge only.

Residual risks

  • Shared kernel (without gVisor). Under runc, a kernel exploit is a container escape. gVisor is recommended and auto-enabled by setup when installed: a sandboxed process then talks to gVisor's user-space kernel, and an escape needs a gVisor bug and a host-kernel bug. See Run agents under gVisor.
  • The launcher holds docker.sock. Its small API and validation are the boundary; audit it first.
  • agentbox parses model output. It has no capabilities, a read-only rootfs and none of your repos mounted. A compromised agentbox could ask the launcher to snapshot or deliver to any repo you own under the allowlist, but only through fixed git operations: copy HEAD, write or delete an agent/<id> branch.
  • No disk quota on workspaces. A runaway agent can fill the disk that holds AGENTBOX_DATA.
  • Remote models send your code to the provider. That's inherent; the gateway just pins where it goes.
  • The planner itself. Keep normal permission prompts in your planner session, not --dangerously-skip-permissions, so a successful injection still needs your approval to act on the host.
  • Single-user design. Everything binds to 127.0.0.1. Don't expose ports 8765 or 8790.

Clone this wiki locally