cd /news/developer-tools/sandbox-cli-is-now-in-public-beta Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-73704] src=sandbox-cli.vercel.app β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

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.

read20 min views1 publishedJul 25, 2026

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

$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.

~/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.

~/.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-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:<gen>-<hash>) so it rebuilds itself whenever the image definition changes. Pinning your own tag opts out of that.

workdir

pathunset β†’ /workspaceWhere the project is mounted and where the guest command starts.

user

sandbox | rootunset β†’ sandboxNon-root by default β€” which is also why agents accept --dangerously-skip-permissions in here; they refuse it as root. On macOS, bind-mount ownership is virtualized, so files are still written as you.

home

pathunset β†’ /sandbox/homeThe fake HOME. Nothing under it is host-connected unless an agent wrapper persists its login there.

hostname

stringunset β†’ sandboxThe container hostname, which is what an agent's prompt shows.

runtime

stringunset β†’ docker default (runc)Any OCI runtime the daemon has registered: kata-runtime for a microVM with its own kernel, runsc for gVisor. Mounts, hardening, allowlist and caches all work unchanged on top.

mounts

list of { host, container, mode }unset β†’ just /workspacehost may start with ~ and may be relative to the config file that declared it. mode is ro unless you say rw. Refusals no key overrides: never /, never your home directory, never an ancestor of it.

env

mapunset β†’ emptyLiteral values injected into the container. Merged key by key, so a project file can add one without wiping your user-level set.

env_allow

listunset β†’ the agent's own suggestionA default-deny allowlist of host variables, forwarded only when actually set. The one list that appends across layers instead of replacing.

secrets

map of { file | command | env }unset β†’ noneBrokered credentials, resolved at run time and forwarded by name. Exactly one source per secret; a command: source can fetch a short-lived token fresh each run.

network.mode

default | none | allowlistunset β†’ defaultallowlist programs a default-deny egress firewall inside the container at startup, then drops back to the non-root user. If it cannot be programmed, the run fails instead of running open.

network.allow

listunset β†’ baseline onlyExtra domains on top of the built-in baseline β€” agent APIs plus the common package registries β€” so npm install and git keep working. Replaces rather than appends, so a project can fully redefine it.

ports

listunset β†’ nothing publishedA spec with no address of its own binds to 127.0.0.1, not every interface β€” the one place sandbox-cli deliberately differs from docker -p. Write 0.0.0.0:3000:3000 to expose it on purpose.

security.no_new_privileges

boolunset β†’ trueBlocks setuid privilege escalation inside the container.

security.cap_drop / cap_add

listunset β†’ [ALL] / noneAll Linux capabilities are dropped, which is essentially free for the non-root sandbox user. Add one back only when a tool genuinely needs it.

security.pids_limit

intunset β†’ 1024A fork-bomb guard set well above real build and agent process counts. 0 disables it.

security.memory / cpus

stringunset β†’ unlimitedOpt-in resource caps, e.g. 2g and 1.5. Empty leaves the container unbounded β€” sandbox-cli measures usage rather than throttling it.

security.seccomp

stringunset β†’ docker's default profilePoint at your own seccomp profile when the default one blocks something you need.

cache.enabled / cache.paths

bool / listunset β†’ false / built-in dirsKeeps npm, pip, cargo and go caches in named docker volumes so a cold install every run stops hurting. No host directory is involved. Extra paths are added to the built-in set.

snapshot.enabled / interval / retention

bool / duration / durationunset β†’ true / 2m / 336hThe crash safety net: the workspace is committed under refs/sandbox/snapshots/ while a run is in flight, never touching your index, HEAD, branches or working tree. sandbox-cli recover reads it back.

Some lists append, the rest replace

mounts and env_allow accumulate across layers β€” a project adds a variable without restating yours β€” and env and secrets overlay key by key. network.allow, ports, cache.paths and the security lists replace instead, so a project can fully redefine a policy, and say β€œnone” with an empty list.

Relative paths follow their file

A mount written as ./data resolves against the directory of the config file that declared it, not your current directory β€” so the same .sandbox.yaml means the same thing no matter where in the repo you run from.

Omitted is not false

enabled, no_new_privileges and pids_limit are tri-state: leaving a key out keeps the inherited value, while writing it out explicitly overrides it β€” which is how a project turns a default-on setting off.

A firewall that stops exfiltration without stopping npm install #

Filesystem isolation does nothing about an agent that has been talked into POSTing your .env somewhere. Flip the allowlist and watch which requests still leave.

api.anthropic.com

the model the agent is running onBaselineregistry.npmjs.org

npm install, still workingBaselinegithub.com

git fetch, git pushBaselinepypi.org

pip installBaselinefiles.pythonhosted.org

the wheels themselvesBaselineraw.githubusercontent.com

install scriptsBaselineinternal.registry.example.com

your private registry β€” added with --allow--allowapi.continue.dev

an agent's own config endpoint, added with --allow--allowpaste.example.net

the exfiltration a prompt-injected agent was talked intoDeniedwebhook.attacker.tld

your .env, POSTed somewhere elseDeniedcrypto-pool.example

a miner the dependency chain brought alongDeniedtelemetry.unknown-vendor.io

phone-home nobody asked forDenied

iptables

inside the container at startup and then dropped back to the non-root user. It fails closed. Domains resolve to IPs once at startup, so hosts behind rotating CDN addresses can still be refused β€” add them explicitly.## Three agents, three branches, one repo, zero collisions

Isolation stops being a tax the moment it lets you do something you could not do before. --worktree

runs each agent in a real git worktree for its own branch, in its own container, so you can start three and go and do something else.

~/projects/app

Your checkout, untouched and still on whatever branch you had. The worktrees live in a sandbox-owned directory, so the project folder stays clean.

~/.config/sandbox/worktrees/app-9f95/<branch>

  • feature-a -p "implement the API"

sandbox-dk0gtrd15s2g

mem 412MiBcpu 82% - feature-b -p "port the tests"

sandbox-9f2la8hq4vzn

mem 308MiBcpu 61% - docs/rewrite -p "rewrite the guide"

sandbox-m4x1pq7bd0cs

mem 196MiBcpu 24%

git diff main...feature-a

sandbox-cli worktree commit feature-a -m "…"

sandbox-cli worktree rm feature-a

You can see what it is doing #

A sandbox you cannot watch is a sandbox you will not trust. sandbox-cli measures β€” it never throttles β€” and reports in three places.

Why only Claude gets a status line

Claude Code has a statusLine

hook, so the gauge lives in its own UI, injected through a managed-settings file that never touches your Claude settings. Neither Gemini CLI nor OpenCode has such a hook. Running them inside tmux to fake one was tried and reverted β€” it made their TUIs render badly, which is a bad trade for a gauge.

For every other agent the answer is sandbox-cli stats

in a second terminal, plus the peak-usage line every run prints when it exits.

shared history

~/.claude/projects/<project> -> /sandbox/home/.claude/projects/-workspace

sandbox-cli claude mounts your host Claude history for the current project so --resume inside the sandbox lists the sessions you started on the host, and sessions you run inside show up outside afterwards. Only that one project's directory is mounted, it is read-write, and --no-sync opts out.

non-interactive run β€” the sticky footer gauge

inside a sandbox-cli claude session β€” Claude's own status line

after every run, interactive included β€” the peak summary

15 agents, one prefix, your flags forwarded verbatim #

sandbox-cli claude --dangerously-skip-permissions

just works: a leading run of sandbox flags is consumed by sandbox, and the first token that is not one ends it β€” everything after goes to the agent untouched. 4 agents are baked into the base image; the rest install themselves into their own persisted home on first use, so you only download what you actually run.

Claude Code

Anthropic

sandbox-cli claude --dangerously-skip-permissions

  • login
  • Run it and follow the prompt β€” a Claude account or ANTHROPIC_API_KEY.
  • persisted at ~/.config/sandbox/agents/claude -> /sandbox/home

forwarded only if set

ANTHROPIC_API_KEY

ANTHROPIC_AUTH_TOKEN

ANTHROPIC_BASE_URL

CLAUDE_CODE_USE_BEDROCK

CLAUDE_CODE_USE_VERTEX

The only wrapper with a live memory/CPU status line in the agent's own UI, and the only one that shares your host conversation history for this project so --resume works on both sides.

Where this sits, including where it loses #

Running an agent in a disposable container is a crowded space. sandbox-cli's edge is code quality, ergonomics and a focused feature set β€” not a hard security boundary. If you need one of those, the table says so.

sandbox-clithis project Built-in agent sandboxesClaude / Codex Docker Sandboxessbx Native OS toolsSeatbelt / Landlock Cloud microVMsE2B, Daytona, …
Isolation strengthHow hard the wall actually is Good β€” Docker + hardening, optional gVisor/Kata Medium β€” OS-level, shared kernel Excellent β€” microVM / Firecracker Good β€” kernel primitives Excellent β€” microVMs
Local, no cloudYour code never leaves the machine Yes Yes Yes Yes No
Persistent agent authLog in once, not every run Excellent β€” dedicated persistent home Varies Good Varies Varies
Package cache persistenceNo cold npm install every run Yes β€” --cache volumes Limited Good Manual Often built-in
Parallel agents (worktrees)Several branches at once, no collisions Excellent β€” built-in --worktree Poor Good Poor Varies
Credential brokerSecrets off the argv and out of history Excellent β€” file / cmd / env sources Basic Good β€” proxy Varies Good
Egress / network controlStop exfiltration, keep installs working Strong β€” allowlist with baselines Basic Strong Varies Strong
Observability / metricsWhat is this thing actually doing Excellent β€” live gauge, stats, summaries Limited Good Poor Varies
Project configPer-repo policy, checked in Excellent β€” .sandbox.yaml Limited Good Poor API / config
Dry-run / previewRead the boundary before trusting it Yes No Varies No Varies
Ease of use High β€” CLI-focused, thorough docs High High Medium Medium β€” setup
Cross-platform Good β€” macOS / Linux / Windows Good Excellent Platform-specific N/A
Docker dependency Yes No Yes No No
Best for Local multi-agent workflows, ergonomics Quick minimal protection Strongest local isolation Lightweight, zero deps Scale & long-running tasks

This is the project’s own read of the landscape, and the ratings for other tools are a snapshot that will age β€” check their docs before choosing. sandbox-cli’s edge is code quality and a focused feature set, not a hard security boundary; for that, reach for microVM tooling (or run sandbox-cli on top of one with --runtime

).

Everywhere Docker runs #

Almost everything works identically across platforms; the differences are all about the boundary the host can provide.

Capability macOSDocker Desktop Linuxnative Docker WindowsDocker Desktop / WSL2
run, agent wrappers, mounts, env, hardening, metrics supported supported supported
--cache, --secret, --worktree, --git, --share supported supported supported
Egress allowlist (--allow)The firewall runs iptables inside the Linux container, so it works wherever the container kernel is Linux. Verified in CI on native Linux; not yet independently verified on Docker Desktop. partly verified supported partly verified
--host-gatewayhost.docker.internal resolves automatically on Docker Desktop; native Linux needs the flag. auto needed auto
/workspace file ownershipOn native Linux, use --user "$(id -u):$(id -g)" if ownership matters. virtualized to you container uid virtualized to you
--runtime kata-runtime / runscDocker Desktop runs containers in its own managed Linux VM and won't let you register custom OCI runtimes β€” you already get a VM boundary from Docker Desktop itself. not available supported not available

Install once. Prefix your agent. Done. #

Needs Docker β€” Docker Desktop on macOS and Windows. Go 1.25+ only if you build from source. The first run builds the base image; every run after that starts immediately.

Uninstalling is symmetrical and cautious: --uninstall

removes the binary and then reports what else is on disk without deleting it β€” because ~/.config/sandbox

holds your agent logins, and silently deleting it would sign you out of everything with no warning. Add --purge

when you mean it.

all 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.

v0.0.1beta.5 Β· verified against the release checksums.txt

Β· installs to ~/.local/bin

Β· no root, no package manager.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @sandbox-cli 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/sandbox-cli-is-now-i…] indexed:0 read:20min 2026-07-25 Β· β€”