this project provides a hardened container virtualization and security sandboxing environment to run untrusted developer or coding agents safely. it utilizes docker-in-docker, gvisor kernel virtualization, and the zero-trust l7 proxy.
the zero-trust l7 proxy is powered by ottergate, which inspects outbound DNS and HTTPS traffic to enforce domain-level access control.
setup and boot infrastructure: build base images, start the nested docker daemon, and spin up the zero trust proxy.
./ac startlist active sandboxes and workspaces: check running containers, status details, and provisioned workspaces.
./ac listcheck status and resource metrics: inspect container health, cpu/memory utilization, and disk space usage.
./ac statusrun agent sandbox in headless mode: execute an agent blueprint in non-interactive background mode.
./ac run pi my_pi_runrun interactive shell inside sandbox: spawn a secure shell inside the agent container for interactive debugging.
./ac shell pi my_pi_debuglimit agent sandbox resources: restrict cpu, memory allocation, or gpu devices on sandbox start.
./ac run pi my_pi_run --cpus 2 --memory 1gview host logs: stream operational logs of the outer docker-in-docker host container.
./ac logs hostview zero trust proxy logs: inspect domain routing, dns resolution, and network allowlist decisions.
./ac logs proxyview gvisor kernel logs: trace user-space kernel events, system calls, and platform logs.
./ac logs gvisorview specific agent logs: retrieve the terminal output of an active or stopped sandbox container.
./ac logs agent my_pi_runclean sandbox credentials: wipe transient git/github/gitlab credentials while preserving local workspace files.
./ac clean my_pi_rundestroy sandbox workspace: stop the sandbox container and delete both its credentials and workspace files.
./ac destroy my_pi_runteardown nested environments: stop the outer host daemon, remove all nested containers, and clear cache storage.
./ac downthe sandbox supports several pre-configured agent blueprints, located in the agents/blueprints directory. you can instantiate any of them using ./ac run <blueprint_name> <instance_name>:
antigravity: specialized sandbox for the antigravity coding agent CLI.gemini: pre-configured container environment tailored for google gemini code assistants.claude: pre-configured container environment tailored for anthropic claude developer agents.hermes: customized runtime environment for the hermes developer agent.codex: optimized developer environment for openai codex and chat-based coding agents.opencode: sandbox designed for open-source code interpreter agents (opencodeinterpreter).pi: sandbox optimized for inflection pi and general-purpose developer agents.base: a clean, minimal developer container with standard debugging and build utilities.
the security boundaries are constructed across nested virtualization, system call virtualization, privilege control, and network policies:
graph TD
Host["π» Physical Host OS (runc)"]
subgraph DinD ["π³ Docker-in-Docker Host (ac-dind-host)"]
direction TB
Dockerd["βοΈ Nested Docker Daemon (with runsc/gVisor & crun)"]
subgraph Net ["π Sandbox Network (172.20.0.0/16 - Internal Only)"]
Agent["π€ coding agent (runtime: runsc / gVisor)"]
Ottergate["𦦠ottergate (runtime: runc)"]
end
ExtNet["π External Network (Bridge to Internet)"]
end
Host -->|runc / privileged| DinD
Agent -->|DNS / L7 HTTP Proxy| Ottergate
Ottergate -->|allowlisted traffic| ExtNet
ExtNet -->|NAT| Internet["π Public Internet"]
style Host fill:#1e1e2e,stroke:#313244,stroke-width:2px,color:#cdd6f4
style DinD fill:#313244,stroke:#45475a,stroke-width:2px,color:#cdd6f4
style Net fill:#181825,stroke:#eba0ac,stroke-width:2px,stroke-dasharray: 5 5,color:#cdd6f4
style Agent fill:#f38ba8,stroke:#a6adc8,stroke-width:2px,color:#11111b
style Ottergate fill:#fab387,stroke:#a6adc8,stroke-width:2px,color:#11111b
the host bootstrap process starts the outer dind container, generates certificates, sets up the nested docker network, and launches default network proxy utilities:
graph TD
Start["π User: ./ac start"] --> DinDHost["π³ Start dind-host Container (runc)"]
DinDHost --> Entrypoint["π entrypoint-dind.sh Runs"]
subgraph DinDHostSetup ["βοΈ Host-Level Bootstrap"]
Entrypoint --> NestedDockerd["π³ Start Nested Docker Daemon (gVisor & crun)"]
Entrypoint --> TLSGen["π Generate TLS Certificates"]
Entrypoint --> BuildBase["π¦ Build agent-base Image"]
Entrypoint --> Ottergate["𦦠Start Ottergate Proxy (runc)"]
Entrypoint --> NetworkRules["π Run update-iptables.sh"]
end
style Start fill:#89b4fa,stroke:#a6adc8,stroke-width:2px,color:#11111b
style DinDHostSetup fill:#1e1e2e,stroke:#313244,stroke-width:2px,color:#cdd6f4
when running a specific sandbox instance, the agent provisioning scripts configure local vault spaces before dropping privileges to start execution:
graph TD
StartRun["π User: ./ac run/shell"] --> ProvisionInstance["π Create instance directory, env & secrets"]
ProvisionInstance --> LaunchAgent["π€ Launch Agent Sandbox (runsc/gVisor)"]
subgraph AgentSetup ["π€ Nested Sandbox Bootstrap"]
LaunchAgent --> InitGuard["π‘οΈ init-guard Runs (root)"]
InitGuard --> CAConfig["π Load CA Certificates"]
InitGuard --> GitConfig["βοΈ Configure git helper"]
InitGuard --> VaultDaemon["π Start vault-daemon (root)"]
InitGuard --> SecureSecrets["π Secure secrets (chmod 0000)"]
InitGuard --> DropPrivs["π€ Drop privileges to node (UID 1000)"]
DropPrivs --> RunAgent["π Exec Agent script / shell"]
end
style StartRun fill:#89b4fa,stroke:#a6adc8,stroke-width:2px,color:#11111b
style AgentSetup fill:#181825,stroke:#eba0ac,stroke-width:2px,color:#11111b
style RunAgent fill:#a6e3a1,stroke:#a6adc8,stroke-width:2px,color:#11111b
tokens are mounted to a secure directory, copied to root-only storage, cleared from memory, and accessed through process verification checks:
sequenceDiagram
autonumber
actor Agent as π€ Agent Process (node)
participant Wrapper as π‘οΈ vault-wrapper (setuid root)
participant Daemon as βοΈ vault-daemon (root)
participant Vault as π /vault/gh_* (root-only)
Note over Agent: Runs 'git clone' or 'gh repo list'
Agent->>Wrapper: Executes wrapped gh/glab command
Note over Wrapper: Escalates to root via setuid
Wrapper->>Daemon: Connects via Unix Socket /vault/vault.sock
Note over Daemon: getsockopt peer credentials gets caller PID and exe path
Daemon->>Daemon: Traces process chain to verify caller
alt Caller is authorized Git/Agent process
Daemon->>Vault: Reads decrypted GitHub/GitLab token
Vault-->>Daemon: Returns token
Daemon-->>Wrapper: Returns token over Socket
Wrapper->>Wrapper: drops privileges to user 'node'
Wrapper->>Agent: Pipes token to gh/glab command
else Caller is unauthorized
Daemon-->>Wrapper: Blocks connection / Returns empty
Wrapper-->>Agent: Access denied
end
the agent network is internal-only, redirecting all outbound connections through custom system hooks to the zero-trust filtering firewall:
graph LR
subgraph Step1 ["1. DNS Resolution"]
Agent["π€ Agent"] -->|Queries domain| DNS["π Ottergate DNS"]
DNS -->|Check allowlist| DNSResult{"Allowed?"}
end
subgraph Step2 ["2. Interception"]
DNSResult -->|Yes| Connect["π syscall: connect"]
DNSResult -->|No| NXDOMAIN["π« Block connection"]
Connect -->|net_proxy.so intercepts| Redirect["π Redirect to 172.20.0.53"]
end
subgraph Step3 ["3. L7 Filtering"]
Redirect -->|Hits Ottergate Proxy| Proxy["π Ottergate Proxy"]
Proxy -->|Inspect SNI/Host| L7Result{"Allowlisted?"}
end
subgraph Step4 ["4. Egress"]
L7Result -->|Yes| Internet["π Public Internet"]
L7Result -->|No| Drop["π« Drop connection"]
end
style Step1 fill:#181825,stroke:#313244,stroke-width:1px,color:#cdd6f4
style Step2 fill:#181825,stroke:#313244,stroke-width:1px,color:#cdd6f4
style Step3 fill:#181825,stroke:#313244,stroke-width:1px,color:#cdd6f4
style Step4 fill:#181825,stroke:#313244,stroke-width:1px,color:#cdd6f4
style Agent fill:#f38ba8,stroke:#a6adc8,stroke-width:2px,color:#11111b
style DNS fill:#89b4fa,stroke:#a6adc8,stroke-width:2px,color:#11111b
style Internet fill:#a6e3a1,stroke:#a6adc8,stroke-width:2px,color:#11111b
the runtime environment uses a nested container layout to segregate privileges, restrict system calls, and inspect outbound requests:
graph TD
subgraph Host ["π» Physical Host OS (runc)"]
subgraph DinD ["π³ Docker-in-Docker Host (runc privileged)"]
subgraph Daemon ["βοΈ Nested Docker Daemon"]
subgraph Network ["π Sandbox Network (172.20.0.0/16)"]
subgraph AgentContainer ["π€ Agent Sandbox (gVisor / runsc)"]
subgraph EnvHooks ["π System Hooks (LD_PRELOAD)"]
AgentApp["π€ Coding Agent Application"]
end
end
Ottergate["𦦠Ottergate Router & Proxy (runc)"]
end
end
end
end
style Host fill:#1e1e2e,stroke:#313244,stroke-width:2px,color:#cdd6f4
style DinD fill:#181825,stroke:#45475a,stroke-width:2px,color:#cdd6f4
style Daemon fill:#313244,stroke:#585b70,stroke-width:2px,color:#cdd6f4
style Network fill:#11111b,stroke:#89b4fa,stroke-dasharray: 5 5,stroke-width:2px,color:#cdd6f4
style AgentContainer fill:#313244,stroke:#f38ba8,stroke-width:2px,color:#cdd6f4
style EnvHooks fill:#181825,stroke:#fab387,stroke-width:2px,color:#cdd6f4
style AgentApp fill:#f5e0dc,stroke:#f2cdcd,stroke-width:2px,color:#11111b
style Ottergate fill:#a6e3a1,stroke:#a6adc8,stroke-width:2px,color:#11111b
author: Lucian BLETAN
licensed under the Apache License, Version 2.0.