Trust-less devboxes for creds-free and network-isolated coding agents
Find a file
claude-hdb a0400606c2 fix(doctor): read the isolation off the bridge, not off the config
Two bugs, one in each direction.

setup-host's new assertion ran 'nft list table bridge claudebox' without
sudo. nft needs root, so it failed with permission denied and printed
"the box-to-box drop is NOT active" about a rule that was demonstrably
there. A check that cries wolf is worse than no check.

And the deeper one: every check so far has asked the CONFIG whether
boxes are isolated. The config is a claim. Incus can accept
security.port_isolation and the kernel can still leave 'isolated off' on
the tap — and then boxes reach each other while every config in sight
says they cannot. That is precisely the shape of the original bug: the
ACL looked airtight and never saw the traffic.

So the doctor now reads the kernel's own view — 'bridge -d link show'
on claudenet's ports — and reports the isolated flag as the fact it is.
If the profile says true and the kernel says off, we learn that in a
second instead of after another ten-minute drill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 02:16:57 +00:00
bin fix: a failed cold mint must say why, and the doctor must find the cause 2026-07-14 00:55:39 +00:00
cloud-init fix: make claude reachable from non-interactive shells in the box 2026-07-13 22:22:50 +00:00
docs fix: boxes could reach each other — isolate them at the bridge 2026-07-14 01:29:33 +00:00
drill fix(doctor): read the isolation off the bridge, not off the config 2026-07-14 02:16:57 +00:00
host fix(doctor): read the isolation off the bridge, not off the config 2026-07-14 02:16:57 +00:00
profiles fix: isolate boxes with the bridge's port-isolation flag, not an nft rule 2026-07-14 01:41:18 +00:00
.gitignore Import claudebox: creds-free, trust-less Claude Code VMs 2026-07-10 15:00:36 +00:00
install.sh fix: install from the public canonical heavy-duty/claudebox 2026-07-10 15:08:18 +00:00
LICENSE Add MIT License to the project 2026-07-11 21:33:51 +01:00
README.md feat: make the command surface a table, add rename and an escape hatch 2026-07-13 20:49:46 +00:00
VERSION feat: make the command surface a table, add rename and an escape hatch 2026-07-13 20:49:46 +00:00

claudebox

A CLI to run headless, trust-less Claude Code in throwaway VMs. One command mints a fresh, network-isolated Incus box with Claude Code installed. The box is the product — you log in and work; destroying it loses nothing you didn't push.

Strictly creds-free. A box ships with everything installed and no credentials — no Claude token, no git PAT, nothing. You authenticate interactively inside the box. The tool never stores or injects a secret. That means there's nothing shared or committed, so it's safe for multiple operators out of the box.

The tool knows nothing about your projects. You just git clone inside a box. A repo can ship an optional .claudebox/ runbook that Claude Code reads and acts on — there is no install step and no host-run setup. See docs/claudebox-design.md for the design rationale.

Install

curl -fsSL https://raw.githubusercontent.com/heavy-duty/claudebox/main/install.sh | bash

Installs the tree to ~/.local/share/claudebox and links claudebox onto your PATH. Re-run any time to upgrade. (No git clone needed.)

One-time host setup (Ubuntu 24.04 / Debian 13)

~/.local/share/claudebox/host/setup-host.sh   # run twice if it adds you to incus-admin (re-login between)

Idempotent. Installs Incus and creates the isolation stack: the claudenet NAT bridge, the claude-isolate ACL (drops all RFC1918/CGNAT/link-local egress), the claude-dev profile, and firewall rules blocking instance → host. All rules re-apply at boot via claudebox-firewall.service — no post-reboot ritual. If the host lacks dnsmasq-base (Debian cloud images skip Recommends): sudo apt-get install -y dnsmasq-base.

Quick start

claudebox new --name work        # mint a fresh, creds-free box (~10 min cold)
claudebox shell work             # enter as the claude user

Inside the box, authenticate as needed:

claude                           # then run /login — copy the URL (press c), open it
                                 #   in YOUR browser, paste the code back. No host CLI needed.
gh auth login                    # or drop a PAT in — your git credentials, your call
git clone https://github.com/you/project && cd project
claude                           # if the repo has .claudebox/, Claude reads it and sets up

Log in once, reuse via snapshots

Because every fresh box is creds-free, re-authenticating each time would be toil. Snapshot an authenticated box and clone from it instead:

claudebox snapshot work authed   # checkpoint after you've logged in
claudebox new --name feature --from work/authed   # clone the authed state into a new box

--from copies the whole box (Claude login, git creds, clones and all) while preserving isolation. You can also claudebox new --name x --from work to clone a box's live state, or roll a box back with claudebox restore work authed.

Forgotten what you called a checkpoint? claudebox info work prints the box's snapshot labels and the --from line to clone one.

Commands

claudebox new --name <box> [--from <src>[/<snap>]] [--vm|--container] [--remote r]
claudebox list                     # list your boxes
claudebox info <box>               # one box: state, IP, snapshot labels
claudebox shell <box>              # enter as the claude user
claudebox exec <box> -- <cmd...>   # run a command in the box
claudebox snapshot <box> [label]   # checkpoint (label defaults to manual-<epoch>)
claudebox restore <box> <snap>     # roll back to a snapshot
claudebox rename <box> <new>       # rename a box (stop it first)
claudebox down <box>               # stop (state kept; `start` resumes)
claudebox start <box>              # start a stopped box
claudebox rm <box> [--force]       # delete the box + its snapshots (asks first)
claudebox incus <box> -- <args...> # escape hatch: any incus command, box resolved
claudebox status                   # deprecated alias for `list`
claudebox help [<command>]         # full help, or one command's page

Every command takes --help, and options come after the command (claudebox list --json). Exit status: 0 ok, 1 it went wrong, 2 you asked wrong.

new fresh-launches from cloud-init, or with --from clones an existing box or snapshot. VM mode (--vm, the default where /dev/kvm exists) is the trust-less target; container mode (auto-fallback, security.nesting=true) is for hosts without nested virt — weaker isolation, dev/test only.

Boxes are just Incus instances

A box is an ordinary Incus instance tagged user.claudebox=1. claudebox wraps the box lifecycle and the isolation model — not all of Incus. It owns a command when it must enforce something Incus can't see: that tag (it will not stop, rename or delete an instance it didn't mint), the isolation stack, or the creds-free snapshot workflow. For everything else, there's the door:

claudebox incus work -- config show        # instance name appended
claudebox incus work -- file push x.tar {}/tmp/   # or placed with {}

The box is resolved and tag-checked; the rest is passed to incus verbatim, and the command is echoed before it runs. If it can move the box off the isolation stack (profile, network, device, security.*), claudebox warns and proceeds — the trust boundary is then yours to keep. See docs/claudebox-design.md for the rule and why the command surface is a table.

Isolation

Dedicated NAT bridge claudenet + Incus claude-isolate ACL dropping all private-range egress, plus host-firewall rules that block instance → host (including the host's public IPs). The box reaches the public internet and nothing else. Entry is incus exec over the local socket only — no inbound path exists. The VM is the trust boundary: Claude can run arbitrary code inside and touch nothing you care about.

Recipes: the .claudebox/ convention

A repo that wants to be easy to stand up in a box ships an optional .claudebox/ folder — a runbook Claude reads and follows (install deps, start services, template env, seed data, smoke-test). It is agent-facing documentation, not a host-executed script. See docs/claudebox-recipe.md.

Uninstall

~/.local/share/claudebox/host/teardown-host.sh               # boxes, network, ACL, profile, firewall
~/.local/share/claudebox/host/teardown-host.sh --purge-incus # ...and Incus itself
rm -rf ~/.local/share/claudebox ~/.local/bin/claudebox       # the CLI

Non-goals

  • No unattended/CI bring-up. The flow is interactive (log in, clone, ask Claude). Reproducible-by-construction provisioning is out of scope.
  • No credential storage or injection by the tool. Boxes are creds-free; snapshots are the reuse mechanism, not a secrets store.