sandbox-cli
every sandbox a VM with its own kernel

A whole machine for the agent. None of it is yours.

sandbox-cli gives any command — a test suite, a build, Claude Code or Codex at full autonomy — a disposable microVM, on your Mac, a Linux machine you control, or the cloud, behind one API. A sandbox starts in a home directory of its own and nothing on your machine is mounted in; egress is an allowlist of names enforced outside the guest.

Apple silicon (macOS 26) or Linux with KVM client on macOS · Linux · WindowsMIT licensed · written in Go

microVMclient · server · guest agent
all releases
$git clone https://github.com/Amitgb14/sandbox-cli
cd sandbox-cli && make studio build
install -d ~/.local/bin
install -m 0755 bin/sandbox-cli bin/sandboxd bin/sandbox-guestd ~/.local/bin/

The first microVM release is not out yet: 0.0.1 is the container design's, so the install script would refuse it. This builds sandbox-cli (with Studio's UI; Node 20+), sandboxd and the guest agent, and installs them side by side; starting sandboxd is one step in the setup guide below.

then

  • $sandbox-cli run -- uname -aa fresh VM, its own kernel
  • $sandbox-cli agent claudea coding agent, its login kept
  • $sandbox-cli listwhat is running, wherever it runs
your machine/sandbox/home
  • awaiting a command…
~80 ms
to a running VM
Firecracker, image cached
<1 ms
from a pool
sandboxes booted ahead
0
host paths mounted
nothing on your machine is mounted in
1
API, three places
your Mac, your Linux box, the cloud
the trade nobody should have to make

Autonomy is what makes agents useful. Your machine is what it puts at risk.

An agent earns its keep the moment it stops asking permission for every edit — and the same flag hands a non-deterministic process your home directory, while prompt injection turns text somebody else wrote into commands your shell runs. The answer is not a better prompt. It is a machine of its own.

Agent on your machine
  • 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
  • A container shares your kernel: one bug from the host
Agent in a sandbox
  • Nothing of yours is mounted — there is nothing to read
  • It starts in its own home directory; nothing comes back but the agent's login
  • Injection lands in a VM that is discarded with the sandbox
  • Its own kernel, behind a hypervisor, not a namespace
12of 12 host locations in reach

This is the default when you run an agent with “Allow All” on your machine. Pick a path to read what is at stake.

Tighten, never loosen

The server's policy is a ceiling every request is resolved against: a request may ask for less network, fewer resources, a narrower allowlist — never more. A repository's own .sandbox.yaml is untrusted and may only tighten your config.

The guest is hostile

The host talks to one agent in the VM over a bounded protocol and never acts on what the guest volunteers. Nothing in the guest can name a host path for the host to read, and the only thing that comes back is the agent's saved login.

Fail closed

A control that was asked for and cannot be delivered refuses the run. A backend that cannot enforce an allowlist says so in its capabilities, and the request is refused — never served open, never quietly offline.

three ways to run it

Your Mac, your own Linux box, or the cloud

sandboxd serves the API on each machine. The same request means the same thing on all three; what differs is what each machine can deliver, and the API says so rather than letting you find out.

Local

your Mac

not yet run on a real Mac
runs on
the native container runtime — a VM per sandbox
needs
macOS 26 on Apple silicon
good for
Iterating on an agent or a harness: offline, no cost per second, nothing to set up beyond the runtime.
docs/local-macos.md

Self-hosted

a Linux machine you control

verified on KVM
runs on
Firecracker microVMs, egress enforced on the host
needs
Linux with /dev/kvm, x86_64 or arm64
good for
Code that cannot leave the building, a team's shared box, many agents at once: nothing phones home.
docs/self-hosting.md

Cloud

hosted

planned
runs on
the same Firecracker nodes, run for you
needs
an API key
good for
Bursts bigger than a laptop, and clients that cannot run VMs at all.
docs/rewrite/PLAN.md
One API in all three. The CLI, the SDKs and Studio cannot tell which they are talking to beyond what GET /v1/capabilities says, and a conformance suite run against an endpoint is what “the same” means. Moving between them is sandbox-cli context use, not a migration.
one API

The CLI is one client. Your code can be another.

Make a sandbox, run something, read what it printed, throw it away. The CLI, curl, and the Python and TypeScript SDKs do it with the same calls — documented in the API reference, with files, background processes, a real terminal over attach, tunnels, snapshots, volumes and an event log on top.

$sandbox-cli run --network none -- echo hello
# hello
what you actually get

Everything it does, by the question you came with

Each card names the flag or setting behind it and whether it is on by default. Nothing here is a plan: what is not built yet is said so in the modes and the comparison.

  • on by default

    A VM per sandbox, with its own kernel

    Firecracker on Linux, the native container runtime on Apple-silicon Macs: either way the guest runs its own kernel behind a hypervisor. Escalating to root inside is root of a machine with nothing of yours in it.

  • on by default

    Nothing of yours is mounted

    No host directory is shared, on any backend. Every process starts in /sandbox/home, the sandbox user's home; code gets in the way it gets onto any machine — the agent or the command runs git clone, or the files API writes it. Nothing comes back to your machine but the agent's saved login.

    sandbox-cli agent claude -p "clone github.com/you/app and fix its failing test"
  • on by default

    The guest is treated as hostile

    The host talks to one agent inside the VM over a bounded, framed protocol and never acts on what the guest volunteers. Files copied out — an agent's saved login — are written without following links.

  • on by default

    Tighten, never loosen

    --profile

    The server's policy is the ceiling; a request may only ask for less. A project's .sandbox.yaml is untrusted and may tighten what your own config says, never widen it. dev warns when a control cannot be delivered; prod refuses.

    sandbox-cli run --profile prod -- make release
  • on by default

    Fail closed

    A control that was asked for and cannot be delivered refuses the run. A backend that cannot enforce an egress allowlist says so in its capabilities, and the request is refused — never served open, never quietly offline.

  • on by default

    Which agent is waiting for you

    agent state

    Working, blocked, idle, done or failed, decided from the agent's process and its conversation: who spoke last, how long ago, and whether it has a terminal somebody can answer at. Never from the agent's wording, so a reworded prompt cannot make it lie. agent wait blocks until an agent is in a state you name; Studio's dashboard counts the ones waiting.

    sandbox-cli agent wait fix-auth --state blocked --timeout 30m
  • opt-in

    Pools: a create is a claim

    pools:

    The server keeps sandboxes of one image booted ahead of requests. A create of that shape takes under a millisecond; environment, name and labels are still the request's own, because the server applies them.

    pools: [{size: 2}]   # in sandboxd's policy
  • on by default

    Suspend, resume, fork

    On Firecracker, suspend a sandbox with its memory and processes and pay nothing while it waits; snapshot a running one and start forks of it in about 20 ms each.

    sandbox-cli snapshot sbx_… && sandbox-cli run --from-snapshot snp_… -- bash
  • opt-in

    Volumes that outlive the sandbox

    --volume

    A named filesystem one sandbox writes and the next one reads: a package cache, a dataset. One live sandbox at a time, read-only enforced by the drive itself, and never mounted on the host.

    sandbox-cli run --volume cache:/sandbox/home/.cache -- npm ci
  • on by default

    Tunnels to a port inside

    Forward a local port to a server listening on the guest's loopback, through the API — a dev server, a debugger — without opening anything on the guest's network.

    sandbox-cli tunnel sbx_… 3000
  • on by default

    One API, three places

    The CLI, the Python and TypeScript SDKs and Studio are clients of the same API, served by sandboxd on your Mac, on a Linux machine you control, or in the cloud. A conformance suite run against an endpoint is what “the same” means.

    sandbox-cli context use box
  • on by default

    Egress is an allowlist of names

    --allow / --deny

    Open by default; under an allowlist only the agent's API, package registries and the names you add get through. The check is by name — TLS SNI, HTTP Host — so a host sharing an allowed address does not ride in on it; deny wins over allow, wildcards included.

    sandbox-cli run --allow internal.registry.example.com -- npm ci
  • on by default

    Enforced outside the guest

    On Linux the firewall and the name-checking proxy run on the host, in a table the guest cannot reach. DNS inside answers only allowlisted names and forwards nothing.

  • on by default

    Change it while it runs

    Narrow or widen a running sandbox's egress, under the same rules as create. No restart, no lost state.

  • on by default

    Environment by name, never by value

    --env

    Values reach the guest and nowhere else: the API returns names only, the audit log records names only. Some names are refused outright, because they are instructions to the loader or the shell rather than settings.

    sandbox-cli run -e NPM_TOKEN -- npm publish
  • opt-in

    Secrets resolved on your machine

    secrets:

    A secret is a reference — a file, a command, a host variable — resolved on the client and handed to the sandbox, never written into an argv or a config file. Long-lived tokens are named when they are recognised.

    secrets: {GITHUB_TOKEN: {command: "gh auth token"}}
  • on, --flag to disable

    Agent logins, kept and contained

    --no-persist-auth

    An agent's login files are copied into each sandbox and back out when it ends, never mounted. prod turns this off entirely, so a refresh token is never in reach of an unattended agent.

  • on by default

    An audit log on the server

    Every create, every process with its argv and exit code, every file read and written, every network change and how each sandbox ended — whichever client asked. Environment variables by name only.

    sandbox-cli events sbx_…
  • opt-in

    Labels

    --label

    Your own metadata on a sandbox: shown by list, filterable, recorded in its audit events. The agent layer labels its runs itself, so a run that fell back to another agent says which one it skipped and why.

    sandbox-cli list --label team=infra
  • on by default

    Sessions outlive the terminal

    Detach, close the laptop, come back: list what is running, follow its output from the start, attach a real terminal, or stop it. A reference is matched against the server's own sandboxes, never resolved by a backend.

    sandbox-cli logs sbx_… · attach · kill
the network half of the problem

An allowlist of names, enforced where the agent cannot reach

A sandbox can still read whatever the agent cloned into it, so the question is where that can go. Egress is open by default. Ask for an allowlist — or run the prod profile, which always does — and only the agent's API, package registries and the names you add get through, checked by name on the host: npm install works and a POST to somebody's webhook does not.

4 of 12 destinations refused
  • api.anthropic.comthe model the agent is running on
    Baseline
  • registry.npmjs.orgnpm install, still working
    Baseline
  • github.comgit fetch, git push
    Baseline
  • pypi.orgpip install
    Baseline
  • files.pythonhosted.orgthe wheels themselves
    Baseline
  • raw.githubusercontent.cominstall scripts
    Baseline
  • internal.registry.example.comyour private registry — added with --allow, where the server's policy permits
    --allow
  • api.continue.devan agent's own config endpoint, added with --allow
    --allow
  • paste.example.netthe exfiltration a prompt-injected agent was talked into
    Denied
  • webhook.attacker.tldyour .env, POSTed somewhere else
    Denied
  • crypto-pool.examplea miner the dependency chain brought along
    Denied
  • telemetry.unknown-vendor.iophone-home nobody asked for
    Denied
Default-deny, enforced on the host, outside the guest: a firewall table per sandbox sends web traffic through a proxy that decides on the name— TLS SNI or HTTP Host — and resolves it fresh per connection, so a host sharing an allowed address does not ride in on it. The guest's DNS answers only allowlisted names. It fails closed: a server that cannot enforce it refuses the request.
where the work happens

A sandbox starts in its own home directory

Every process starts in /sandbox/home, the sandbox user's home. Nothing on your machine is mounted in: code gets there the way it gets onto any machine — the agent or the command runs git clone, or the files API writes it. When the sandbox ends, nothing comes back to your machine but the agent's saved login.

$sandbox-cli run -- pwd
# /sandbox/home
$sandbox-cli agent claude -p "clone github.com/you/app and fix its failing test"
supervision

A sandbox outlives the terminal that started it

sandboxd owns every sandbox, not the client that asked for it. Detach, close the laptop, come back from another machine: the same four commands find it, wherever it runs.

What exists right now, on the sandboxd this context points at.

$ sandbox-cli list
ID NAME STATE IMAGE NETWORK CREATED LABELS
sbx_6b6b467a88ae4dae tests running sandbox-base allowlist 2026-10-02 03:38:32 -
sbx_31c4aa440aa0b3ae build running sandbox-base allowlist 2026-10-02 03:38:32 team=infra
--label team=infra keeps only the sandboxes carrying that label

The same listing whichever machine it is: your Mac, a Linux box, the cloud — sandbox-cli context use picks which. Labels are your own metadata; the agent layer adds its own (agent, route.id, route.from), so an agent's runs, and a run that fell back to another agent, are findable by what they were for.

A kill -9 on sandbox-cli leaves the sandbox running — sandboxd owns it, not the client that started it, and --detach means to. These four commands are how you get back to it; events shows what it did.

observability

And afterwards, what it did

The server records every sandbox's events — whichever client asked: its policy and environment variable names, every process with its argv and exit code, files read and written, network changes, and how it ended. Values are never written down.

$sandbox-cli events sbx_cf20dd8c24c71989
# 2026-10-02 03:17:02 sandbox.created image sandbox-base · network none · env SECRET_TOKEN · labels team=infra
# 2026-10-02 03:17:02 process.started pid 1 · sh -c echo hi > note.txt; exit 4
# 2026-10-02 03:17:02 process.exited pid 1 exit 4 after 0s
# 2026-10-02 03:17:02 sandbox.terminated request
coding agents

12 agents under one prefix, logins kept between runs

sandbox-cli agent claude --dangerously-skip-permissions is run with an agent's conveniences on top: its login copied in and back out, its own environment variables forwarded when set, and everything after the sandbox flags handed to the agent untouched. None of it is required to use a sandbox.

Claude Code

sandbox-cli agent claude

in the base image
$sandbox-cli agent 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

Its login files (.claude/.credentials.json and .claude.json) are copied into each sandbox and back out when the run ends; your own ~/.claude is never read or written. --fallback codex starts codex instead when the provider is not answering, probed before a sandbox is made; codex runs with its own login, not a resumed conversation.

Fallbacks when a provider is down, and several agents at once

--fallback probes each agent's provider before launch and starts the first one that answers. Several agents are several independent runs, each in its own sandbox: start them with --detach and check on them with agent state.

$sandbox-cli agent claude --fallback codex -p "fix the flaky test" # codex if claude's provider is down
$sandbox-cli agent claude --detach -p "clone github.com/you/app and fix issue 12"
$sandbox-cli agent codex --detach -- exec "clone github.com/you/app and fix issue 31"
$sandbox-cli agent state # what each one is doing
the alternatives, honestly

Where this sits, including where it loses

Running untrusted code somewhere safer is a crowded space. This compares kinds of tool rather than products, so it stays true; where a kind varies, the cell says so.

sandbox-clithis projectAgents' own sandboxespolicy inside the agentContainer sandboxesa container per agentOS sandboxingSeatbelt / LandlockHosted sandbox APIsmicroVMs in a provider's cloud
IsolationHow hard the wall actually isA VM per sandbox: its own kernel, behind a hypervisorProcess rules the agent applies to itselfNamespaces on the host's shared kernelKernel-enforced per process, on your kernelA VM per sandbox
Your filesWhat is reachable by defaultNothing mounted; the agent clones what it needsYour filesystem, minus what the rules forbidThe directories you mount, read-writeYour filesystem, minus what the rules forbidNothing; you upload what it needs
Runs where your code isWithout sending it anywhereYour Mac, or a Linux machine you controlYesYesYesNo — the provider's cloud
Self-hosted, nothing phoning homeFor code that cannot leave the buildingsandboxd on your machine; no external control planeNot a serverYour machine, your daemonNot a serverVaries; often their control plane in your cloud at best
One API in every placeLaptop, own server, cloudThe same API, checked by one conformance suiteNo APIThe engine's API, local onlyNo APITheirs, in their cloud
Egress controlStop exfiltration, keep installs workingAllowlist by name, enforced on the host, deny winsSettings in the agentVaries; often open by defaultCoarse: on or offPer-sandbox rules, where offered
Start timeFrom request to a running command~80 ms on Firecracker; under 1 ms from a poolNone — no VMSub-secondNoneFast, plus the network round-trip
Snapshots, fork, suspendKeep a sandbox without paying for itOn Firecracker; not yet on a MacNoRareNoUsually
Coding agentsLogins, fallbacksTwelve agents; logins kept; fallbacksBuilt for one agentSome, per toolYou wire it yourselfAn SDK; you build the rest
Audit logWhat did it run, and how did it endEvery action, on the server; env by name onlyThe agent's own transcriptThe engine's eventsNoVaries
Where it losesNeeds KVM or macOS 26 on Apple silicon; the cloud mode is not open yetThe wall is the agent's own promiseOne kernel bug from your machineDifferent tools per OS, and your files stay in reachYour code leaves the building; you pay per second

This is the project’s own read of the landscape, by kind of tool rather than by product, and a kind varies more than a table can show — check the tool you are weighing before choosing. What sandbox-cli adds is a VM boundary that runs where your code already is, with the same API on your laptop, your server and a cloud.

What this table does not claim. A secret handed to a sandbox reaches its environment, where the agent can read it with printenv— a broker that injects the credential so the agent never holds it is not built, and the posture is to make a leaked one cheap instead: short-lived values, an allowlist, prod’s refusal to copy logins in. A row that cannot be defended against the code gets changed here.

platform support

The client runs anywhere. Sandboxes need a VM.

Where a feature is missing on a platform, the server says so in its capabilities and refuses the request rather than running a weaker one.

CapabilitymacOSApple silicon, macOS 26Linuxwith KVMElsewhereWindows, Intel Macs
Run sandboxes on this machinemacOS 26 on Apple silicon, with the native container runtime; Linux with /dev/kvm. Intel Macs and Windows run the client against a sandboxd elsewhere.supportedsupportedno — client only
Backendcontainer runtimeFirecrackerdoes not apply
Egress allowlist by nameOn Linux the firewall and proxy run on the host and need root. Without root, and on the macOS backend today, a sandbox gets no network or — where the operator permits — open egress; an allowlist request is refused there rather than served open.not yetyes, as rootdoes not apply
Suspend, snapshot, forknot yetsupporteddoes not apply
Volumesnot yetsupporteddoes not apply
The client: run, agent, shell, exec, list, attach, eventssupportedsupportedsupported
setup

From a cold machine to a verified sandbox

Pick where sandboxes will run. Every path ends with doctor, because installing is the easy half and what this sandboxd can actually deliver is a property of the machine.

Before you start: The macOS backend was written and tested on Linux against a fake runtime that runs the real guest agent. Its first runs on a real Mac (macOS 26.1) boot a sandbox in under a second, but the full check has not run yet. Expect rough edges; docs/local-macos.md lists the open points, and the Linux paths are the verified ones.

  1. 1

    Have the runtime

    container system start

    macOS 26 or later, on Apple silicon. Each sandbox is a VM of the runtime with a kernel of its own.

  2. 2

    Build and install

    git clone https://github.com/Amitgb14/sandbox-cli && cd sandbox-cli
    make studio build   # Studio's UI (Node 20+), then bin/sandbox-cli, bin/sandboxd, bin/sandbox-guestd
    install -d ~/.local/bin
    install -m 0755 bin/sandbox-cli bin/sandboxd bin/sandbox-guestd ~/.local/bin/
    export PATH="$HOME/.local/bin:$PATH"   # this shell; put the same line in ~/.zshrc or ~/.bashrc
    command -v sandboxd sandbox-cli        # both must print a path under ~/.local/bin

    No published release has sandboxd yet (0.0.1 is the container design's, client only), so this builds from a checkout: Go 1.25+, and Node 20+ for Studio's UI. sandbox-cli, sandboxd and the guest agent go side by side into ~/.local/bin; sandboxd is installed, not started. Run this as yourself, not root. If command -v prints nothing, ~/.local/bin is not on your PATH: the export line puts it there, and the same line in your shell's startup file keeps it there.

  3. 3

    Start sandboxd as a launch agent

    curl -fsSL https://raw.githubusercontent.com/Amitgb14/sandbox-cli/main/packaging/launchd/dev.sandbox.sandboxd.plist \
      | sed "s#/usr/local/bin/sandboxd#$HOME/.local/bin/sandboxd#" \
      > ~/Library/LaunchAgents/dev.sandbox.sandboxd.plist
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.sandbox.sandboxd.plist

    The launch agent in the repository runs /usr/local/bin/sandboxd; the sed points it at the copy the script installed in ~/.local/bin, so nothing needs root. It listens on a unix socket only you can open, which is the CLI's default context. Egress here is none, or open if your policy allows it: the macOS backend does not enforce an allowlist yet, so it does not claim one.

  4. 4

    Ask what this sandboxd can deliver

    sandbox-cli doctor

    Prints the backend, the API version, the network policy's default and ceiling, the capabilities (egress allowlist, suspend, snapshots, volumes, audit) and the limits. A request for something missing from that list is refused, never served weaker.

  5. 5

    Run something

    sandbox-cli run -- uname -a
    sandbox-cli agent claude

    The first run builds the image's root disk, which takes a while; later ones start in about 80 ms on Firecracker. A sandbox starts in /sandbox/home, its own home directory, with nothing of your machine mounted in: ask the agent to clone what it needs.

  6. 6

    Open Studio

    sandbox-cli studio

    The same sandboxes in a browser: launch a command or an agent, use its terminal, watch its output, files and events. Served by sandbox-cli on a loopback port for the current context; open the address it prints, token and all.

The full setup guide, with troubleshooting →
get started

One build: client, server, guest agent.

The first microVM release is not out yet, so for now you build from a checkout: Go 1.25+, and Node 20+ for Studio's UI. On Linux and Apple-silicon Macs, install all three; elsewhere, the client, which talks to a sandboxd somewhere else.

Uninstalling is cautious: --uninstall removes the binaries and reports what else is on disk — ~/.config/sandbox holds your agent logins, and sandboxd's state directory your volumes. Add --purge when you mean it.

  1. 1

    Remove the binaries

    curl -fsSL https://raw.githubusercontent.com/Amitgb14/sandbox-cli/main/install.sh | sh -s -- --uninstall

    Deletes sandbox-cli, sandboxd and the guest agent from ~/.local/bin (it checks /usr/local/bin too, and reports a file it may not remove rather than stopping), then reports what else is on disk without touching it. A running sandboxd keeps running until you stop its launch agent or unit.

  2. 2

    Then decide about logins and volumes

    sh install.sh --uninstall --purge

    --purge also deletes ~/.config/sandbox — your config and every saved agent login — and sandboxd's state directory, which holds image disks, your volumes and the audit log. It is a separate flag because signing you out of every agent and deleting your volumes is not something an uninstaller should do on its own.

microVMclient · server · guest agent
all releases
$git clone https://github.com/Amitgb14/sandbox-cli
cd sandbox-cli && make studio build
install -d ~/.local/bin
install -m 0755 bin/sandbox-cli bin/sandboxd bin/sandbox-guestd ~/.local/bin/

The first microVM release is not out yet: 0.0.1 is the container design's, so the install script would refuse it. This builds sandbox-cli (with Studio's UI; Node 20+), sandboxd and the guest agent, and installs them side by side; starting sandboxd is one step in the setup guide below.

verified against the release checksums.txt · installs to ~/.local/bin · no root, no package manager.