140 lines
4.9 KiB
Markdown
140 lines
4.9 KiB
Markdown
# claudebox
|
|
|
|
Run [Claude Code](https://docs.anthropic.com/en/docs/claude-code) inside a [bubblewrap](https://github.com/containers/bubblewrap) sandbox with an allowlisted environment, explicit filesystem mounts, and a minimal PATH.
|
|
|
|
SSH keys, GPG/age secrets, cloud tokens, and Tailscale state stay completely invisible to the AI agent. If a secret is accessible inside the sandbox, it's a bug.
|
|
|
|
## Quick start
|
|
|
|
```bash
|
|
nix run git+https://git.toph.so/toph/claudebox
|
|
```
|
|
|
|
Or add to your flake:
|
|
|
|
```nix
|
|
{
|
|
inputs.claudebox.url = "git+https://git.toph.so/toph/claudebox";
|
|
}
|
|
```
|
|
|
|
Then add `inputs.claudebox.packages.${system}.default` to your `environment.systemPackages` or home-manager packages.
|
|
|
|
## What it does
|
|
|
|
- Starts Claude Code inside a bwrap namespace with `--clearenv`
|
|
- Only allowlisted env vars enter the sandbox (HOME, PATH, TERM, EDITOR, LANG, ANTHROPIC_API_KEY if set)
|
|
- Mounts CWD read-write, Nix store read-only, everything else is tmpfs
|
|
- Provides `nix shell` and [comma](https://github.com/nix-community/comma) (`, <tool>`) so Claude can install tools on demand
|
|
- Injects a SANDBOX.md so Claude knows it's sandboxed and how to get tools
|
|
- Pre-configures git identity and safe.directory from host
|
|
|
|
## Flags
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `--yes`, `-y` | Skip the env audit and launch immediately |
|
|
| `--dry-run` | Print the bwrap command without executing |
|
|
| `--check` | Verify prerequisites and exit |
|
|
| `--shell` | Drop into a bash shell instead of Claude Code |
|
|
| `--gc` | Remove stale per-project instance dirs and exit |
|
|
| `--with-ssh` | Forward $SSH_AUTH_SOCK into the sandbox (requires running ssh-agent) |
|
|
| `--ssh-key <path>` | Mount a private key file read-only into the sandbox ~/.ssh/ (repeatable) |
|
|
| `--` | Pass remaining args to Claude Code |
|
|
|
|
## Env vars
|
|
|
|
**Env files (preferred)** — define vars without polluting your shell:
|
|
|
|
`~/.claudebox/env` — global, loaded on every launch:
|
|
```bash
|
|
ANTHROPIC_API_KEY=sk-ant-...
|
|
MY_GLOBAL_VAR=value
|
|
```
|
|
|
|
`<project>/.claudebox.env` — per-project, loaded when present:
|
|
```bash
|
|
DATABASE_URL=postgres://localhost/myapp
|
|
SOME_PROJECT_VAR=value
|
|
```
|
|
|
|
Add `.claudebox.env` to your `.gitignore` if it contains secrets.
|
|
|
|
**Pass-through** — inject host vars already set in your shell:
|
|
```bash
|
|
CLAUDEBOX_EXTRA_ENV=MY_VAR,OTHER_VAR claudebox
|
|
```
|
|
|
|
All injected vars appear in the `[+]` section of the env audit.
|
|
|
|
## SSH
|
|
|
|
SSH is opt-in. By default no keys or agent socket cross the sandbox boundary, which means git push/pull over SSH remotes won't work. Two mechanisms are available — pick whichever matches your workflow.
|
|
|
|
### `--with-ssh` (agent forwarding)
|
|
|
|
Forwards `$SSH_AUTH_SOCK` into the sandbox so any keys loaded in your ssh-agent are usable inside. Your private key files are never mounted; only the agent socket is.
|
|
|
|
Start an agent before launching claudebox. The agent dies with the shell that started it, so don't expect it to survive across terminals.
|
|
|
|
Bash:
|
|
```bash
|
|
eval "$(ssh-agent)"
|
|
ssh-add ~/.ssh/id_ed25519
|
|
claudebox --with-ssh
|
|
```
|
|
|
|
Fish:
|
|
```fish
|
|
eval (ssh-agent -c)
|
|
ssh-add ~/.ssh/id_ed25519
|
|
claudebox --with-ssh
|
|
```
|
|
|
|
If `--with-ssh` is passed but no agent is running, claudebox warns and continues without forwarding.
|
|
|
|
### `--ssh-key <path>` (explicit key files)
|
|
|
|
Mounts a specific private key (and matching `.pub`, if present) read-only into the sandbox at `~/.ssh/<basename>`. Repeatable — pass it multiple times for multiple keys.
|
|
|
|
```bash
|
|
claudebox --ssh-key ~/.ssh/id_ed25519
|
|
claudebox --ssh-key ~/.ssh/id_work --ssh-key ~/.ssh/id_personal
|
|
```
|
|
|
|
Prefer this when you don't have an agent running, or when you want to scope exactly which keys the sandbox can use regardless of what's loaded in the agent.
|
|
|
|
### known_hosts
|
|
|
|
When either flag is active, `~/.ssh/known_hosts` is mounted read-only (if it exists) so SSH host verification works without prompting.
|
|
|
|
Both flags can be combined.
|
|
|
|
## How it works
|
|
|
|
```
|
|
~/.claudebox/ # persistent config dir (host)
|
|
├── SANDBOX.md # managed by claudebox, overwritten each launch
|
|
├── history.jsonl # conversation history
|
|
├── .credentials.json # Claude Code credentials (if present)
|
|
└── projects/
|
|
└── <16-char-hex>/ # per-project instance dir (keyed by canonical git root)
|
|
└── project-root # records the canonical path for this instance
|
|
|
|
Inside the sandbox:
|
|
~/.claude → bind-mounted from host (plugins, skills, hooks, MCP all visible)
|
|
~/.claude/projects → bind-mounted from ~/.claudebox/projects/<hash>/ (per-project isolation)
|
|
~/.claude/history.jsonl → bind-mounted from ~/.claudebox/history.jsonl
|
|
~/.claude/SANDBOX.md → bind-mounted from ~/.claudebox/SANDBOX.md
|
|
```
|
|
|
|
Each project gets an isolated `~/.claude/projects/` directory inside the sandbox, so conversation history and project state are separated per repo. Git worktrees share the same instance dir as their main worktree.
|
|
|
|
## Requirements
|
|
|
|
- NixOS or Nix with flakes enabled
|
|
- User namespaces (enabled by default on NixOS)
|
|
|
|
## License
|
|
|
|
MIT
|