Sandbox-CLI is now in public beta Sandbox-CLI, an open-source tool that runs coding agents like Claude Code and Codex inside disposable Docker containers with strict isolation, is now in public beta. The tool mounts only the user's project directory, blocks access to home directories and credentials, and enforces default-deny environment variables to prevent prompt injection attacks. It is MIT-licensed, written in Go, and available for macOS, Linux, and Windows. Give the agent full autonomy. Give it nothing else. sandbox-cli runs Claude Code, Codex, Gemini and twelve more coding agents inside a disposable Docker container. Only the project you point it at is mounted; HOME is a fake ephemeral path and your SSH keys, cloud credentials and browser cookies are not there to be read. Needs Docker. Nothing else. macOS · Linux · WindowsMIT licensed · written in Go all releases https://github.com/Amitgb14/sandbox-cli/releases $curl -fsSL https://raw.githubusercontent.com/Amitgb14/sandbox-cli/main/install.sh \ | sh -s -- --version 0.0.1beta.5 Detects your OS and CPU, verifies the archive against the release checksums.txt, and installs to ~/.local/bin/sandbox-cli. No root, no package manager. then $sandbox-cli claude Claude Code, contained $sandbox-cli run -- bash a shell in the sandbox $sandbox-cli run --dry-run -- npm test print the docker argv, run nothing - awaiting a command… - 1 - host path mounted - your project, at /workspace - 15 - agents wrapped - one prefix, flags forwarded verbatim - 0 - host creds forwarded - default-deny env allowlist - --rm - every container - nothing survives the run “Allow All” is the mode that makes agents useful. It is also the one that scares you. Agents earn their keep the moment they stop asking permission for every edit. But the same flag that unblocks the work hands a non-deterministic process your entire home directory — and prompt injection turns text somebody else wrote into commands your shell runs. - Reads ~/.ssh, ~/.aws, cloud tokens, browser cookies - One hallucinated path and the blast radius is your whole disk - A poisoned README turns into local execution - The only thing enforcing trust is the prompt - Those paths were never mounted — there is nothing to read - The blast radius is /workspace: the repo it was editing anyway - Injection lands in a container that dies on exit - The kernel enforces trust, not a paragraph of instructions This is the default when you run an agent with “Allow All” on your machine. Pick a path to read what is at stake. One function decides everything runtime.BuildArgs is pure and deterministic: config in, docker argv out. It is the single choke point for what the container can reach, and it is exhaustively unit-tested against a golden output so the boundary cannot drift silently. Three refusals no flag can override Never mount /, never mount your home directory, never mount an ancestor of it. ResolveWorkspace enforces those before anything else runs, and there is no configuration key that turns them off. Nothing crosses that you did not name Host environment variables are default-deny. Each agent wrapper ships a small suggested allowlist applied only if the value is set, and everything else needs an explicit --env or --mount. Twenty-three capabilities, one prefix Everything below ships today and is reachable from a flag or a .sandbox.yaml key. Filter by the question you came with. - on by default One host path, mounted on purpose The project you chose is bind-mounted at /workspace and nothing else is host-connected. HOME, /etc and / inside the container are ephemeral and destroyed on exit. php ~/projects/app - /workspace the only host-connected path - on by default Refusals you cannot configure away sandbox-cli refuses to mount /, your home directory, or any ancestor of it as the workspace. That check lives in ResolveWorkspace and no flag, config file or env var overrides it. - on by default One pure function builds the argv --dry-run Every isolation decision funnels through runtime.BuildArgs — deterministic, no I/O, exhaustively unit-tested against a golden output. --dry-run prints the exact docker command and exits, so you can read the boundary before you trust it. sandbox-cli run --dry-run -- npm test - on by default Hardened container by default --no-hardening Every run drops all Linux capabilities, forbids privilege escalation, and caps the process count to blunt fork bombs. Non-root by default, which is also why agents accept --dangerously-skip-permissions in here. --cap-drop ALL --security-opt no-new-privileges --pids-limit 1024 - opt-in Stronger isolation on request --runtime Point a run at any OCI runtime the daemon knows: kata-runtime for a microVM with its own kernel, runsc for gVisor's userspace syscall filter. Mounts, hardening, allowlist, caches and secrets all work unchanged on top. sandbox-cli claude --runtime kata-runtime - on by default Default-deny environment --env-allow Nothing from your host environment crosses the boundary unless you name it. Each agent wrapper ships a small suggested allowlist — ANTHROPIC API KEY and friends — applied only if the value is actually set. - opt-in Credential broker --secret Resolve a secret at run time from a file, a host command, or a host env var, and forward it by name. The raw value never appears on the docker argv, in --dry-run output, in config, or in your shell history — and cmd: sources can be short-lived tokens fetched fresh each run. --secret GITHUB TOKEN=cmd:'gh auth token' - on, --flag to disable Logins that survive --rm --no-persist-auth Each agent gets its own sandbox-owned directory bind-mounted as the container's whole HOME, so you authenticate once. It is separate from your real ~/.claude — the sandbox never reads or writes your host agent config. php ~/.config/sandbox/agents/claude - /sandbox/home - opt-in Egress allowlist --allow Switch outbound traffic to default-deny with an in-container firewall. DNS, established flows and a baseline of agent APIs and package registries stay open, so npm install and git keep working while arbitrary exfiltration from a prompt-injected agent does not. sandbox-cli claude --allow internal.registry.example.com - on by default Fails closed The firewall is programmed at startup with NET ADMIN — added only in this mode — and the run then drops back to the non-root sandbox user. If setup errors, the run fails rather than silently continuing wide open. - opt-in Publish a port when you want to look --publish No container port is reachable from the host until you ask. When you do, a spec that names no address binds to 127.0.0.1 rather than every interface — the one place sandbox-cli deliberately differs from docker -p. Put your dev-server ports in .sandbox.yaml and stop typing them. sandbox-cli run -P 3000 -- npm run dev - opt-in Reach host services deliberately --host-gateway An agent can talk to an MCP server on your machine through host.docker.internal. That resolves automatically on Docker Desktop; on Linux this flag maps it, and --add-host handles anything else. - opt-in Parallel agents on real git worktrees --worktree Run several agents at once, each on its own branch, in its own container, with no collisions. The worktree lives in a sandbox-owned directory so your checkout stays clean, and the branch shows up in your repo immediately. sandbox-cli claude --worktree feature-a -- -p 'implement A' - opt-in Addressable by branch, never by cd worktree list, path, git, commit and rm all take the branch name. worktree git forwards everything after it straight to git — output and exit code included — so your config, hooks and commit signing still apply. sandbox-cli worktree commit feature-a -m 'implement A' - opt-in Commits attributed to you --git Forwards your host user.name and user.email and marks the workspace trusted, so git in the container stops complaining about dubious ownership and the agent's commits carry your name. - opt-in A channel between sandboxes --share Two sandboxes cannot see each other — that is the point, but it leaves no way to hand over an API contract. --share mounts one host directory at /shared in every sandbox that asks for it. Then just say so in the prompt. - opt-in Pasted image paths that resolve --paste Your terminal pastes an image as an absolute host path, which names nothing inside a container. This mounts ~/Desktop, ~/Downloads and ~/Pictures read-only at their own host paths so the path resolves. Opt-in, because it widens what the agent can read. - opt-in Package caches that persist --cache Containers are --rm, so a cold npm install every run gets old fast. --cache keeps npm, pip, cargo and go caches in named Docker volumes across runs — no host directory involved. - on by default Layered project config Built-in defaults, then ~/.config/sandbox/config.yaml, then the nearest .sandbox.yaml walking up from cwd, then flags. sandbox-cli config show prints what actually won. sandbox-cli init scaffold .sandbox.yaml - on, --flag to disable Live resource gauge --no-metrics Non-interactive runs pin a memory/CPU/elapsed gauge to the bottom of the terminal with the workspace's git branch at the right — the thing that tells parallel worktree sandboxes apart at a glance. Measurement only; no limits are imposed. sandbox-cli │ mem 512MiB/7.6GiB ▓░░░░░░ cpu 82% · 0m47s git:feature/login - on, --flag to disable A status line inside Claude --no-statusline Claude Code renders the container's live memory and CPU in its own UI, injected through a managed-settings file that never touches your own Claude settings. Deliberately limited to claude: no other agent has a status-line hook, and running them under tmux to fake one made their TUIs render badly. sandbox · mem 412MiB · cpu 82% git:feature/login - on by default Peak summary on every run Interactive sessions own the screen, so instead of drawing over them sandbox-cli samples in the background and prints one line when the run exits. You still get the numbers for a twelve-minute Claude session. sandbox-cli: peak mem 412MiB · cpu peak 138% · 12m04s · git:feature/login - opt-in Watch every sandbox at once sandbox-cli stats is a refreshing table of all running sandbox containers in a second terminal — the answer for the agents that have no status line of their own. --once for a scriptable snapshot. sandbox-cli stats --interval 1s Build the command. Read the argv. Then decide. Every flag resolves into a plain docker invocation, and --dry-run prints it before anything executes. Toggle real options and watch the boundary widen or tighten — the arrow beside each flag tells you which way it moves. agent sandbox-cli claude --allow --dry-run Highlighted lines are the ones your toggles added. Hover any line for what it does. The only host paths in the whole command are the --mount sources — count them, and that is the blast radius. Everything above has a .sandbox.yaml key Flags are for one run; a .sandbox.yaml committed at the root of the repo is the boundary everyone on the project gets by default. Nothing here is required — sandbox-cli init scaffolds the file, and every key falls back to a working default. precedence — later wins, key by key - 1config.Default Built-in defaults Non-root sandbox user, /workspace, a fake HOME, all capabilities dropped. - 2you, everywhere ~/.config/sandbox/config.yaml Preferences that follow you across every project on this machine. - 3the project Nearest .sandbox.yaml Found by walking up from the current directory. Commit it; everyone on the repo gets the same boundary. - 4this one run Command-line flags Scalar flags override the file for one run; the list-shaped ones — --mount, --env-allow, --allow, --publish — add to what it declared rather than replacing it. sandbox-cli config show prints the merged result for the directory you are standing in, and sandbox-cli config path says which files it read. .sandbox.yaml .sandbox.yaml — commit this with the project Only /workspace is mounted; HOME is fake and dies with the container. env allow: host vars forwarded ONLY if they are set - ANTHROPIC API KEY - OPENAI API KEY network: mode: default default | none | allowlist sandbox-cli init writes a fuller, fully-commented version of this file into the current directory. Zero config is required — every key below has a working default. Every key, and what you get without it image stringunset → built-in sandbox-baseThe base image tag is content-addressed sandbox-base: