Run AI coding agents in hardened container sandboxes.
AI coding agents run arbitrary code on your machine. clampdown confines them to a hardened container sandbox: filesystem access is restricted to your project, network egress is limited to the APIs the agent needs, and every tool container the agent spawns gets the same enforcement.
Blue = sidecar. Yellow = agent. Green = tool containers. Red text = untrusted egress. Green text = trusted egress. Border color = namespace trust level (see legend in diagram).
clampdown runs three container types with different privilege levels:
| Sidecar | Agent | Tool (nested) | |
|---|---|---|---|
| Purpose | Container runtime + firewall | AI agent process | Tools the agent spawns |
| Base image | FROM scratch (no shell, no libc) |
Alpine | User-chosen |
| Capabilities | 17 (SYS_ADMIN, NET_ADMIN, ...) |
0 (cap-drop=ALL) |
10 default (effective set empty) |
| Seccomp | ~70 blocked | ~115 blocked | ~115 blocked + inherited sidecar |
| Landlock | No (incompatible with mount()) |
workdir RW, rootfs RO | Derived from bind mounts |
| Secrets | Registry credentials (opt-in) | None (sk-proxy dummy key) |
None |
| Network egress | N/A | Deny + allowlist only | Allow, private CIDRs blocked |
| Rootfs | Read-only | Read-only | Read-only |
| Runs as | root (userns-mapped) | Non-root | Non-root (hook-enforced) |
| SELinux | container_engine_t |
container_t |
container_t |
| AppArmor | unconfined | confined | confined |
- The sidecar is privileged enough to run podman and enforce firewall rules, but has no shell and no libc so it cannot be repurposed.
- The agent never holds real API keys — it gets a dummy key and talks to a local auth proxy that injects the real credentials. Even if the agent connects to the upstream API directly, the dummy key produces a 401.
- Tool containers have open egress to the public internet but hold no secrets: they cannot reach private networks, and credentials are never forwarded unless explicitly opted in.
clampdown treats the agent as an untrusted process. Not because current models are
malicious, but because the attack surface is real: prompt injection can hijack an
agent's actions, jailbreaks can override its instructions, and even well-behaved
models run arbitrary code that may do things you didn't ask for. If the agent can
curl, cat ~/.ssh, or podman run --privileged, the question is when — not
whether — something goes wrong.
Every defense in clampdown enforces from outside the agent's process. Filesystem restrictions are kernel-level Landlock rulesets applied before the agent binary starts. Network policy is iptables chains installed by the sidecar, in a network namespace the agent shares but cannot configure. Seccomp profiles are inherited from the container runtime — the agent cannot modify or remove them. OCI hooks validate every container the agent creates before its entrypoint runs, and there is no flag to skip them.
None of these controls depend on the agent cooperating. A fully compromised agent — one that ignores its system prompt entirely and tries to escape — hits the same kernel-enforced walls as one following instructions. That's the point.
The agent runs inside a zero-capability container: cap-drop=ALL, read-only rootfs,
no-new-privileges, and a seccomp profile that blocks ~115 syscalls including all
known kernel exploit primitives (io_uring, userfaultfd, BPF, perf_event, splice/tee).
When the agent needs to run a tool — a compiler, a test runner, a shell command — it
sends a podman run request to the sidecar's API socket. The sidecar intercepts every
container creation through two OCI hooks that run synchronously before the container
process starts:
precreate (seal-inject): rewrites the container entrypoint to sandbox-seal,
which applies Landlock and cleans up unsafe file descriptors before execing the real
command. The hook also assigns a non-root UID mapped outside the container's user
namespace, mounts hidepid=2 on /proc, and injects masked paths over sensitive
kernel interfaces (/proc/kcore, /proc/sysrq-trigger, and nine others).
createRuntime (security-policy): validates the final OCI config against 15
security checks — blocking privileged mode, disallowed capabilities, host namespace
sharing, unsafe bind mounts, dangerous devices, and RW re-mounts of protected paths.
A container that fails any check is killed before its entrypoint runs.
Both hooks apply to every podman run the agent issues. There is no opt-out.
Landlock is a Linux kernel LSM that enforces filesystem access control at the kernel level, beneath and independent of container bind mounts and Unix permission bits.
sandbox-seal applies a Landlock ruleset inside the agent process just before it
execs the agent binary. The rules cannot be lifted after exec — Landlock policies are
inherited across exec and can only be made more restrictive, never relaxed.
Agent policy:
- Read-write + execute: your working directory (the project being edited)
- Read + execute: system directories (
/usr,/bin,/lib,/etc, …) - No access: everything else — host home, other projects, sensitive kernel paths
Tool containers launched by the agent get a derived policy based on their mount list: writable access is granted only to paths explicitly bind-mounted into the container.
Landlock V3 (kernel ≥ 6.2) is a hard requirement. The launcher refuses to start if Landlock is absent. Kernel ≥ 6.12 is recommended for full feature coverage (V4 TCP connect at 6.7, V5 IoctlDev at 6.10, V6 IPC scoping at 6.12). V7 audit logging requires 6.15+.
The agent container shares the sidecar's network namespace. All egress is controlled by iptables rules the sidecar installs at startup — not by application-layer filtering that the agent could bypass.
Agent (default: deny): outbound connections are blocked except for an explicit allowlist of domains required by the agent (API endpoints, auth, telemetry). The allowlist is resolved to IPs at session startup and installed as iptables ACCEPT rules. DNS queries are rate-limited to 10 requests per second.
Tool containers (default: allow): outbound is open to the public internet, but all RFC 1918 addresses, loopback, link-local, and IPv6 ULA ranges are permanently blocked. Tool containers cannot reach the host, the sidecar, or other containers by internal IP.
Private CIDRs are blocked for both the agent and tool containers regardless of policy.
Both defaults are configurable (--agent-policy, --pod-policy), and rules can be
adjusted at runtime without restarting the session — see
Runtime network control.
For the full technical reference — seccomp profile tables, iptables chains, Landlock access sets, capability lists, OCI hook pipeline, and masked path inventory — see DIAGRAM.md.
| Requirement | Version | Notes |
|---|---|---|
| Linux | — | Landlock is Linux-only |
| Kernel | ≥ 6.2 | Hard requirement. Session refuses to start below this. |
| Kernel | ≥ 6.12 | Recommended. V4 TCP connect (6.7), V5 IoctlDev (6.10), V6 IPC scoping (6.12). V7 audit logging (6.15). |
| rootless podman | any recent | or Docker (rootful with a warning) or nerdctl |
| Go | ≥ 1.23 | Build-time only |
Verify Landlock is active on your kernel:
cat /sys/kernel/security/lsm # must contain "landlock"git clone https://github.com/89luca89/clampdown
cd clampdown
make all # builds sidecar image, agent images, and launcher binary
make install # copies binary to ~/.local/bin/clampdownmake all builds four container images (clampdown-sidecar, clampdown-proxy,
clampdown-claude, clampdown-opencode) and the clampdown launcher binary. Images
are rebuilt only when their source changes (stamp files).
Store API keys in ~/.config/clampdown/clampdownrc (created once, used by all sessions):
# ~/.config/clampdown/clampdownrc
ANTHROPIC_API_KEY=sk-ant-...Per-project overrides go in .clampdownrc at the root of each project. Project values
take precedence over the global file:
# /path/to/project/.clampdownrc
ANTHROPIC_API_KEY=sk-ant-... # project-specific keyThen run:
clampdown claude
# OpenCode (supports multiple providers)
clampdown opencode
# Run against a specific directory (defaults to $PWD)
clampdown claude --workdir /path/to/project
# Pass flags through to the agent
clampdown claude -- --model claude-opus-4-5Alternatively, pass keys via environment:
ANTHROPIC_API_KEY=sk-ant-... clampdown claudeThe first run pulls base images and builds a per-project container storage cache under
~/.cache/clampdown/. Subsequent runs start faster.
| Agent | Command | Supported provider keys |
|---|---|---|
| Claude Code | clampdown claude |
ANTHROPIC_API_KEY |
| OpenCode | clampdown opencode |
ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, GEMINI_API_KEY, GROQ_API_KEY, DEEPSEEK_API_KEY, MISTRAL_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY, OPENCODE_API_KEY |
Keys are passed to the auth proxy, not the agent. The first matching key (from host
environment or .clampdownrc) activates the proxy for that provider. The agent
receives a dummy key (sk-proxy) and a base URL pointing at the local proxy.
All options can be set on the CLI, in config.json, or (for environment-variable-mapped
options) via env.
clampdown [options] <agent> [-- agent-flags...]
| Flag | Default | Env | Description |
|---|---|---|---|
--agent-policy |
deny |
SANDBOX_AGENT_POLICY |
Agent egress default: deny (allowlist only) or allow |
--agent-allow |
— | SANDBOX_AGENT_ALLOW |
Extra domains for the agent allowlist (comma-separated) |
--pod-policy |
allow |
SANDBOX_POD_POLICY |
Tool container egress default: allow or deny |
The agent's egress allowlist includes the domains required by the agent (API endpoints, auth, telemetry). Private CIDRs (RFC 1918, loopback, link-local, IPv6 ULA) are always blocked for both agent and tool containers, regardless of policy.
| Flag | Default | Description |
|---|---|---|
--allow-hooks |
off | Allow agent to modify .git/hooks/ (read-only by default) |
--protect |
— | Additional paths to protect read-only (repeatable; trailing / = directory) |
--mask |
— | Additional paths to mask - prevent read (repeatable; trailing / = directory) |
Protected paths are always read-only inside the agent and nested containers, regardless
of flags: .git/config, .gitmodules, .clampdownrc, .devcontainer, .envrc,
.idea, .mcp.json, .vscode. User --protect paths get the same enforcement.
Protected paths propagate into nested containers via recursive bind mounts; explicit
RW re-mounts are blocked by the security-policy hook.
| Flag | Default | Env | Description |
|---|---|---|---|
--memory |
4g |
SANDBOX_MEMORY |
Memory limit for agent and tool containers |
--cpus |
4 |
SANDBOX_CPUS |
CPU limit |
Credentials are opt-in. Nothing is forwarded by default.
| Flag | Description |
|---|---|
--gitconfig |
Forward ~/.gitconfig read-only into tool containers |
--gh |
Forward ~/.config/gh read-only into tool containers (GitHub CLI auth) |
--ssh |
Forward SSH_AUTH_SOCK into tool containers |
| Flag | Default | Env | Description |
|---|---|---|---|
--registry-auth |
off | SANDBOX_REGISTRY_AUTH |
Forward host registry credentials to the agent |
--require-digest |
warn |
SANDBOX_REQUIRE_DIGEST |
Image digest enforcement: warn or block |
| Flag | Default | Description |
|---|---|---|
--runtime |
auto | Container runtime: podman, docker, nerdctl |
--disable-tripwire |
off | Don't kill session on protected path modification (still restores on exit) |
--log-level |
info |
debug, info, warn, error |
# List all running sessions
clampdown list
# Stop a session and clean up its containers
clampdown delete -s <session-id>
# Remove all cached container storage for the current project
clampdown pruneNetwork rules can be adjusted while a session is running:
# Allow the agent to reach a host on a specific port
clampdown network agent allow -s <session-id> example.com --port 443
# Block a host
clampdown network agent block -s <session-id> example.com --port 443
# Remove all dynamic agent rules (returns to startup state)
clampdown network agent reset -s <session-id>
# Same commands for tool containers (pod)
clampdown network pod allow -s <session-id> db.internal --port 5432
clampdown network pod reset -s <session-id>
# Show current rules for a session
clampdown network list -s <session-id>Targets can be hostnames, IP addresses, or CIDRs. Hostnames are resolved to IPs at the time the rule is applied.
Tool containers pull from the sidecar's isolated registry. To make a local host image available inside the sandbox:
clampdown image push -s <session-id> myimage:latestImages already present in the sidecar (same image ID) are skipped.
Persistent defaults live in $XDG_CONFIG_HOME/clampdown/config.json (typically
~/.config/clampdown/config.json). Any CLI option can be set here.
{
"agent_policy": "deny",
"agent_allow": "registry.mycompany.com",
"gitconfig": true,
"gh": true,
"memory": "8g",
"cpus": "8",
"require_digest": "block"
}KEY=VALUE files for API key configuration. Two locations are merged; project
overrides global:
~/.config/clampdown/clampdownrc— global$workdir/.clampdownrc— per-project
# ~/.config/clampdown/clampdownrc
ANTHROPIC_API_KEY=sk-ant-...
# myproject/.clampdownrc
ANTHROPIC_API_KEY=sk-ant-... # project-specific keyKeys from .clampdownrc are passed to the auth proxy, not to the agent container.
Lines starting with # are comments. Values may be quoted with " or '.
make all # sidecar + proxy + agent images + launcher
make test # all unit tests (no podman required)
make test-integration # integration tests (requires podman + internet)
make sidecar # sidecar image only
make proxy # auth proxy image only
make claude # claude agent image only
make opencode # opencode agent image only
make launcher # launcher binary only
make install # install launcher to ~/.local/bin/
make clean # remove built images and binariesSee CONTRIBUTING.md for contribution guidelines.
VM backend
- lima/colima-based runtime that isolates at the hypervisor boundary.
- Required for workloads needing
NET_ADMIN, KVM, or GPU access. - One long-lived VM per user, multiple agent sessions sharing it.
- Required for workloads needing
Structured audit trail
- a tamper-evident JSONL event log written by the sidecar (outside agent reach): every podman API call, OCI hook decision (allow/deny/mutate), iptables egress hit, and tripwire trigger. SHA-256 hash chain per entry. Queryable from the host, with optional webhook notifications (Slack/HTTP) on deny or kill events.
More agents
- Gemini CLI
- OpenAI Codex.
macOS support
- depends on the VM backend; Landlock and iptables are Linux-only.