Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.agent-vault.dev/llms.txt

Use this file to discover all available pages before exploring further.

agent-vault vault run has two isolation modes:
  • --isolation=host (default) — forks the agent on the host with HTTPS_PROXY and CA-trust env vars pointing at Agent Vault. Cooperative: a misbehaving or malicious agent can unset the env, spawn a subprocess that doesn’t inherit them, use raw sockets, or exfiltrate over DNS.
  • --isolation=container — launches the agent inside a Docker container whose egress is locked down with iptables. Non-cooperative: the only TCP destination the container can reach is the Agent Vault proxy. Everything else is dropped at the kernel.
Container mode is opt-in while it stabilizes. Host mode remains the default.

Quick start

agent-vault vault run --isolation=container -- claude
First run takes ~60s while the isolation image builds. Subsequent runs reuse the cached image.

What’s inside the container

  • Image: agent-vault/isolation:<hash>, built on first use from an embedded Dockerfile. Debian-slim + Node 22 + @anthropic-ai/claude-code + iptables / gosu / curl / git / python3.
  • Network: a dedicated per-invocation Docker bridge network (agent-vault-<session>). Not the default bridge — other containers you’re running cannot reach the forwarder.
  • User: claude (UID != 0), dropped via gosu after init-firewall.sh runs as root. --security-opt=no-new-privileges + --cap-drop=ALL with only NET_ADMIN / NET_RAW (for iptables), SETUID / SETGID (so gosu can change UID), and KILL (so tini at PID 1 can forward TTY signals across the UID boundary) added. Docker doesn’t grant container caps as ambient caps to non-root processes, so claude post-gosu has an empty effective cap set regardless.
  • Egress policy: OUTPUT DROP by default on both IPv4 (iptables) and IPv6 (ip6tables). ACCEPT only for loopback, ESTABLISHED/RELATED replies, and the two Agent Vault ports at host.docker.internal over IPv4 (the MITM path is v4-only by construction — we resolve via getent ahostsv4). No DNS rule: host.docker.internal is resolved via /etc/hosts (docker run --add-host=host.docker.internal:host-gateway), closing the DNS-exfiltration channel.
  • Mounts:
    • $PWD → /workspace (read-write, your project)
    • ~/.agent-vault/isolation/ca-<session>.pem → /etc/agent-vault/ca.pem (read-only, MITM CA)
    • agent-vault-claude-home-<session> → /home/claude/.claude (per-invocation by default; removed after the container exits. See --home-volume-shared for a persistent docker volume, or --share-agent-dir to bind-mount your host ~/.claude instead.)

Flags

FlagDefaultDescription
--isolationhosthost (default) or container. Also read from AGENT_VAULT_ISOLATION.
--imageOverride the bundled image. Use for your own base (different agent, pinned versions).
--mountExtra bind mount src:dst[:ro]; repeatable. Host paths are symlink-resolved before validation; binds into ~/.agent-vault/ or /var/run/docker.sock are rejected.
--keepfalseOmit --rm so the container is available for docker inspect / docker logs after exit.
--no-firewallfalseDebug only. Skip init-firewall.sh; container has unrestricted egress. Prints a loud warning.
--home-volume-sharedfalseShare /home/claude/.claude across invocations via a persistent docker volume. Default is per-invocation (auth doesn’t persist, but concurrent runs can’t corrupt each other).
--share-agent-dirfalseBind-mount the host’s agent state dir (~/.claude) into the container so it reuses your host login (auth, project history, MCP config). Mutually exclusive with --home-volume-shared.

Bundled image vs --image

The default image pins @anthropic-ai/claude-code at build time. If you want a specific version, or a different agent entirely, provide your own image:
# Build your own image (any Dockerfile that runs as root at entry,
# supports `gosu`, has `iptables` in PATH, and ends with:
#   ENTRYPOINT ["/usr/local/sbin/entrypoint.sh"]
# where entrypoint.sh calls init-firewall.sh then execs gosu <user> "$@"
# — the bundled assets in internal/isolation/assets/ are the reference.)
docker build -t my-org/my-agent-isolation:v1 .

agent-vault vault run --isolation=container --image=my-org/my-agent-isolation:v1 -- claude

Agent state directory: three modes

/home/claude/.claude inside the container holds auth tokens, MCP config, and session history. There are three ways to back it:
  1. Per-invocation docker volume (default). Fresh each run → you’ll be prompted to log in again on every vault run. Safe to run concurrent sessions because every container has its own volume.
  2. Shared docker volume (--home-volume-shared). Persistent named docker volume across invocations. Auth persists.
    agent-vault vault run --isolation=container --home-volume-shared -- claude
    
    Don’t run two --home-volume-shared sessions concurrently — there’s no locking, and the volume’s contents will race.
  3. Bind-mount the host’s ~/.claude (--share-agent-dir). Reuses your existing host login — starting the container feels identical to running claude on the host (no onboarding, your project history is there). Mutually exclusive with --home-volume-shared.
    agent-vault vault run --isolation=container --share-agent-dir -- claude
    
    Concurrency matches the status quo of running claude directly on the host — session files are UUID-keyed, so two concurrent containers behave the same as two host-level Claude sessions. On Linux, agent-vault passes HOST_UID/HOST_GID so the container’s claude user matches your host uid — writes to the bind mount land owned by you, not the baked-in container uid. macOS auth bridge. Claude stores its OAuth credential in the macOS Keychain, not on disk, so the bind mount alone doesn’t carry the login across. When --share-agent-dir is set on a Mac host, agent-vault calls security find-generic-password -s "Claude Code-credentials" -w once to extract the credential and writes it to ~/.claude/.credentials.json (mode 0600) — the path Linux Claude reads. Only runs if the file is absent; the container (or you) can refresh it thereafter. First use may trigger a Keychain confirmation prompt. Trade-off: the credential now exists as a filesystem-readable file on your Mac, slightly weaker than Keychain-only storage. If you’d rather avoid that, skip --share-agent-dir (or rm ~/.claude/.credentials.json and /login inside the container — Claude will write a fresh file itself).
Security note: container isolation protects vault-brokered credentials (Stripe, GitHub, etc.) by forcing egress through Agent Vault. Your Claude login in ~/.claude is the agent’s own identity — sharing it with the agent inside the container is strictly a UX choice, not a credential exposure, since the agent already needs that identity to function. The genuinely sensitive ~/.agent-vault/ directory stays off-limits in all three modes.

Threat model

In scope. The agent (and any subprocess it spawns) cannot:
  • Reach destinations outside host.docker.internal:<agent-vault-port> via any network method (HTTPS, raw sockets, ICMP, whatever) — iptables DROPs at the kernel before the packet leaves the container.
  • Exfiltrate over DNS — no DNS rule, no resolver, host.docker.internal is baked into /etc/hosts.
  • Inspect or mutate host state outside the workspace and its own ephemeral home volume.
  • Drop its own iptables rules — gosu claude runs UID != 0, Docker doesn’t grant container capabilities as ambient caps to non-root processes.
Out of scope.
  • Container escapes via kernel exploits. We rely on Docker’s standard isolation.
  • Exfiltration through Agent Vault to whitelisted upstreams (e.g. Anthropic API). Agent Vault’s service catalog and credential-injection policy govern that; the container just guarantees traffic can’t bypass the broker.
  • Side channels (timing, resource usage).

Platform support

  • Linux: supported. Requires Docker 20.10+ for --add-host=host.docker.internal:host-gateway.
  • macOS (Docker Desktop): supported.
  • Windows: not supported in v1. vault run --isolation=container errors on Windows.

Troubleshooting

“docker not found in PATH” — install Docker Desktop (macOS) or Docker Engine (Linux) and make sure docker is on PATH. “host.docker.internal not resolvable” inside the container — Docker version is too old. Upgrade to 20.10+ on Linux. First run hangs — the image build pulls node:22-bookworm-slim and runs apt-get install + npm install -g @anthropic-ai/claude-code. ~60 seconds on a fast connection; a slow network or corporate registry proxy can make it much longer. Watch docker ps in another terminal to confirm progress. iptables -S OUTPUT in the container shows DROP but the agent still makes external calls — that’s impossible unless you passed --no-firewall. Check for the loud warning banner on startup. Containers leak after crashes — agent-vault vault run --isolation=container runs PruneStaleNetworks at startup, removing any agent-vault-* networks older than 60 seconds with zero attached containers. The 60-second grace window prevents racing invocations from deleting each other’s freshly-created networks.