From 71790d714be50ca3c47f05144490c53b70dd526e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=BChl?= Date: Thu, 9 Apr 2026 11:02:11 +0200 Subject: [PATCH] docs(01): create phase plan Co-Authored-By: Claude Opus 4.6 --- .planning/ROADMAP.md | 9 +- .../01-minimal-viable-sandbox/01-01-PLAN.md | 380 ++++++++++++++++++ .../01-minimal-viable-sandbox/01-02-PLAN.md | 208 ++++++++++ 3 files changed, 592 insertions(+), 5 deletions(-) create mode 100644 .planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md create mode 100644 .planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 56e15d0..150a643 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -28,12 +28,11 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Secret paths (~/.ssh, ~/.gnupg, ~/.aws, ~/.config/gcloud, age keys, /var/lib/tailscale) are not visible inside the sandbox 4. Claude can run `curl https://example.com`, `git status`, `, jq --help` (comma), and `nix shell nixpkgs#python3 -c python3 --version` inside the sandbox 5. Ctrl+C terminates the session cleanly; exit code from Claude passes through to the caller -**Plans**: TBD +**Plans:** 2 plans Plans: -- [ ] 01-01: TBD -- [ ] 01-02: TBD -- [ ] 01-03: TBD +- [ ] 01-01-PLAN.md -- Create flake.nix and claudebox.sh with complete bwrap sandbox +- [ ] 01-02-PLAN.md -- Build verification and manual sandbox smoke test ### Phase 2: Env Audit and CLI Polish **Goal**: User can review exactly what enters the sandbox before launch, and has diagnostic tools for troubleshooting @@ -68,6 +67,6 @@ Phases execute in numeric order: 1 -> 2 -> 3 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Minimal Viable Sandbox | 0/3 | Not started | - | +| 1. Minimal Viable Sandbox | 0/2 | Planned | - | | 2. Env Audit and CLI Polish | 0/1 | Not started | - | | 3. Sandbox-Aware Prompting | 0/1 | Not started | - | diff --git a/.planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md b/.planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md new file mode 100644 index 0000000..2f90379 --- /dev/null +++ b/.planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md @@ -0,0 +1,380 @@ +--- +phase: 01-minimal-viable-sandbox +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - flake.nix + - claudebox.sh +autonomous: true +requirements: + - SAND-01 + - SAND-02 + - SAND-03 + - SAND-04 + - SAND-05 + - SAND-06 + - SAND-07 + - SAND-08 + - SAND-09 + - SAND-10 + - SAND-11 + - SAND-12 + - SAND-13 + - SAND-14 + - SAND-15 + - TOOL-01 + - TOOL-02 + - TOOL-03 + - GIT-01 + - GIT-02 + - NIX-01 + - NIX-02 + - NIX-03 + - UX-06 + +must_haves: + truths: + - "Running `nix build` in the project root produces a working `claudebox` binary" + - "claudebox launches Claude Code inside bwrap with only allowlisted env vars" + - "Secret paths (~/.ssh, ~/.gnupg, ~/.aws, etc.) are not visible inside the sandbox" + - "Git works inside the sandbox with user identity from host" + - "comma and nix shell work inside the sandbox for on-demand tool installation" + - "Ctrl+C terminates the session cleanly; exit code passes through" + artifacts: + - path: "flake.nix" + provides: "Nix flake with claudebox as default package" + contains: "writeShellApplication" + - path: "claudebox.sh" + provides: "Shell script body with bwrap sandbox invocation" + contains: "exec bwrap" + key_links: + - from: "flake.nix" + to: "claudebox.sh" + via: "builtins.readFile ./claudebox.sh" + pattern: "builtins.readFile ./claudebox.sh" + - from: "claudebox.sh" + to: "bwrap" + via: "exec bwrap with --clearenv" + pattern: "exec bwrap.*--clearenv" + - from: "claudebox.sh" + to: "claude" + via: "CLAUDE_BIN resolved from host PATH before clearenv" + pattern: "CLAUDE_BIN=.*command -v claude" +--- + + +Create the complete claudebox Nix flake and shell script that launches Claude Code inside a bubblewrap sandbox with full environment isolation, filesystem isolation, secret hiding, git support, and tool provisioning. + +Purpose: This is the entire deliverable for Phase 1 -- a working `claudebox` command. +Output: `flake.nix` and `claudebox.sh` in the project root. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/01-minimal-viable-sandbox/01-CONTEXT.md +@.planning/phases/01-minimal-viable-sandbox/01-RESEARCH.md + + + + + + Task 1: Create flake.nix with all inputs and writeShellApplication + flake.nix + + .planning/phases/01-minimal-viable-sandbox/01-RESEARCH.md + + +Create `flake.nix` in the project root with the following exact structure: + +```nix +{ + description = "claudebox - sandboxed Claude Code"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + nix-index-database = { + url = "github:nix-community/nix-index-database"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, nix-index-database, ... }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + comma-with-db = nix-index-database.packages.${system}.comma-with-db; + in { + packages.${system} = { + claudebox = pkgs.writeShellApplication { + name = "claudebox"; + runtimeInputs = [ + pkgs.bubblewrap + pkgs.coreutils + pkgs.git + pkgs.curl + pkgs.jq + pkgs.ripgrep + pkgs.fd + pkgs.nix + comma-with-db + pkgs.bash + pkgs.nodejs + ]; + text = builtins.readFile ./claudebox.sh; + }; + default = self.packages.${system}.claudebox; + }; + }; +} +``` + +Key points per user decisions: +- Per D-02: `comma-with-db` comes from the `nix-index-database` flake, using `packages.${system}.comma-with-db` (not legacyPackages). +- Per NIX-01/NIX-02: Flake with pinned inputs. `nixpkgs.follows` ensures single nixpkgs instance. +- Per NIX-03: `default` package alias so `nix run` and `nix profile install` work. +- Per SAND-01/SAND-10: `writeShellApplication` produces the binary and wires runtimeInputs into PATH. +- Claude Code is NOT in runtimeInputs -- it's discovered from host PATH at runtime (see Research Pattern 5). + + + grep -q 'writeShellApplication' flake.nix && grep -q 'comma-with-db' flake.nix && grep -q 'nix-index-database' flake.nix && grep -q 'builtins.readFile ./claudebox.sh' flake.nix && echo "PASS" || echo "FAIL" + + + - flake.nix contains `description = "claudebox - sandboxed Claude Code"` + - flake.nix contains `nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"` + - flake.nix contains `nix-index-database.packages.${system}.comma-with-db` + - flake.nix contains `name = "claudebox"` + - flake.nix contains `builtins.readFile ./claudebox.sh` + - flake.nix contains all 11 runtimeInputs: bubblewrap, coreutils, git, curl, jq, ripgrep, fd, nix, comma-with-db, bash, nodejs + - flake.nix contains `default = self.packages.${system}.claudebox` + + flake.nix exists with correct flake structure, all runtime dependencies, and readFile of claudebox.sh + + + + Task 2: Create claudebox.sh with complete bwrap sandbox logic + claudebox.sh + + .planning/phases/01-minimal-viable-sandbox/01-RESEARCH.md + .planning/phases/01-minimal-viable-sandbox/01-CONTEXT.md + + +Create `claudebox.sh` in the project root. This is the shell script body read by `writeShellApplication` (which adds `set -euo pipefail` and prepends runtimeInputs to PATH automatically). The script must implement the following sections in order: + +**Section 1: Resolve claude binary from host PATH (before clearenv strips it)** + +```bash +CLAUDE_BIN=$(command -v claude) || { + echo "error: claude not found in PATH" >&2 + echo "Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code" >&2 + exit 1 +} +``` + +**Section 2: Capture sandbox PATH** + +The runtimeInputs-constructed PATH is available as `$PATH` at this point. Capture it for passing into the sandbox: +```bash +SANDBOX_PATH="$PATH" +``` + +**Section 3: Record CWD** + +```bash +CWD=$(pwd) +``` + +**Section 4: Ensure ~/.claudebox exists** + +```bash +mkdir -p "$HOME/.claudebox" +``` + +**Section 5: Generate minimal .gitconfig (per D-05)** + +Read host git identity, write a temp .gitconfig, set up cleanup trap: +```bash +GIT_NAME=$(git config --global user.name 2>/dev/null || echo "Claude User") +GIT_EMAIL=$(git config --global user.email 2>/dev/null || echo "claude@localhost") + +GITCONFIG_TMP=$(mktemp) +trap 'rm -f "$GITCONFIG_TMP"' EXIT + +cat > "$GITCONFIG_TMP" < + + grep -q 'exec bwrap' claudebox.sh && grep -q 'clearenv' claudebox.sh && grep -q 'CLAUDE_BIN' claudebox.sh && grep -q 'CLAUDEBOX_EXTRA_ENV' claudebox.sh && grep -q 'GITCONFIG_TMP' claudebox.sh && grep -q 'dangerously-skip-permissions' claudebox.sh && echo "PASS" || echo "FAIL" + + + - claudebox.sh does NOT contain `#!/bin/bash` or `set -euo pipefail` (writeShellApplication adds these) + - claudebox.sh contains `CLAUDE_BIN=$(command -v claude)` with error handling on failure + - claudebox.sh contains `SANDBOX_PATH="$PATH"` + - claudebox.sh contains `mkdir -p "$HOME/.claudebox"` + - claudebox.sh contains git config reading: `git config --global user.name` and `git config --global user.email` + - claudebox.sh contains `GITCONFIG_TMP=$(mktemp)` and `trap 'rm -f "$GITCONFIG_TMP"' EXIT` + - claudebox.sh contains `safe.directory = *` in the generated gitconfig + - claudebox.sh contains ENV_ARGS array with --setenv for HOME, USER, PATH, SHELL, TMPDIR, XDG_RUNTIME_DIR, NIX_SSL_CERT_FILE, SSL_CERT_FILE + - claudebox.sh contains HOST_ALLOWLIST with TERM, EDITOR, LANG, LC_ALL, ANTHROPIC_API_KEY + - claudebox.sh contains CLAUDEBOX_EXTRA_ENV parsing with `IFS=',' read -ra EXTRAS` + - claudebox.sh contains `exec bwrap` with `--clearenv` + - claudebox.sh contains `--tmpfs /` before any other mount + - claudebox.sh contains `--ro-bind /nix/store /nix/store` + - claudebox.sh contains `--bind /nix/var/nix /nix/var/nix` (rw, not ro-bind) + - claudebox.sh contains `--ro-bind /etc/resolv.conf /etc/resolv.conf` + - claudebox.sh contains `--ro-bind /etc/ssl /etc/ssl` AND `--ro-bind /etc/static /etc/static` + - claudebox.sh contains `--ro-bind /etc/nix /etc/nix` + - claudebox.sh contains `--ro-bind /etc/passwd /etc/passwd` and `--ro-bind /etc/group /etc/group` + - claudebox.sh contains `--symlink` for `/usr/bin/env` + - claudebox.sh contains `--tmpfs "$HOME"` BEFORE the claudebox and CWD bind mounts + - claudebox.sh contains `--bind "$HOME/.claudebox" "$HOME/.claude"` + - claudebox.sh contains `--ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig"` + - claudebox.sh contains `--bind "$CWD" "$CWD"` and `--chdir "$CWD"` + - claudebox.sh contains `-- "$CLAUDE_BIN" --dangerously-skip-permissions "$@"` at the end + - claudebox.sh does NOT contain any mount of ~/.ssh, ~/.gnupg, ~/.aws, ~/.config/gcloud, or /var/lib/tailscale + + claudebox.sh exists with complete bwrap invocation covering all SAND-*, TOOL-*, GIT-*, and UX-06 requirements + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Host -> Sandbox | Environment variables cross from untrusted host env into sandbox via allowlist | +| Host -> Sandbox | Filesystem paths cross via explicit bind mounts | +| Sandbox -> Host | CWD is mounted read-write, so Claude can modify project files (intended) | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-01-01 | Information Disclosure | Environment variables | mitigate | `--clearenv` + explicit allowlist. Only SAND-03 vars pass through. CLAUDEBOX_EXTRA_ENV is user-opt-in. | +| T-01-02 | Information Disclosure | Secret filesystem paths | mitigate | tmpfs root + tmpfs HOME. Only explicit bind mounts visible. ~/.ssh, ~/.gnupg, ~/.aws never mounted. Verify by absence in mount list. | +| T-01-03 | Tampering | Host filesystem via CWD mount | accept | CWD is intentionally rw -- Claude needs to edit project files. Scope is limited to the single directory. | +| T-01-04 | Information Disclosure | Git credential helpers | mitigate | Host ~/.gitconfig is NOT mounted. A minimal generated .gitconfig with only user.name, user.email, and safe.directory is used instead. No credential helper config enters sandbox. | +| T-01-05 | Elevation of Privilege | Nix daemon socket access | accept | Daemon socket is rw to allow `nix shell`. The daemon runs as root on host but nix client operations are normal user operations. Nix daemon has its own access controls. | +| T-01-06 | Information Disclosure | /etc/passwd, /etc/group | accept | Read-only mount of user database. Contains usernames/UIDs only, no password hashes (those are in /etc/shadow which is not mounted). Required for basic tool functionality. | +| T-01-07 | Spoofing | CLAUDE_BIN resolution | accept | claude binary is resolved from host PATH before sandbox. If attacker controls host PATH, they already have full host access. Not a sandbox boundary issue. | + + + +After both tasks complete: +1. `nix flake check` passes (or at least doesn't error on flake structure) +2. `grep -c 'exec bwrap' claudebox.sh` returns 1 +3. `grep -c 'clearenv' claudebox.sh` returns 1 +4. No secret paths appear in claudebox.sh mounts: `grep -E '\.ssh|\.gnupg|\.aws|gcloud|tailscale' claudebox.sh` returns nothing + + + +- flake.nix and claudebox.sh exist in project root +- flake.nix defines claudebox as default package with all 11 runtimeInputs +- claudebox.sh implements complete bwrap sandbox with env allowlist, filesystem isolation, git identity, and tool provisioning +- All 24 phase requirements (SAND-01 through SAND-15, TOOL-01 through TOOL-03, GIT-01, GIT-02, NIX-01 through NIX-03, UX-06) are addressed + + + +After completion, create `.planning/phases/01-minimal-viable-sandbox/01-01-SUMMARY.md` + diff --git a/.planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md b/.planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md new file mode 100644 index 0000000..dc89aee --- /dev/null +++ b/.planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md @@ -0,0 +1,208 @@ +--- +phase: 01-minimal-viable-sandbox +plan: 02 +type: execute +wave: 2 +depends_on: ["01-01"] +files_modified: [] +autonomous: false +requirements: + - NIX-03 + - SAND-02 + - SAND-03 + - SAND-04 + - SAND-05 + - SAND-06 + - SAND-09 + - SAND-10 + - SAND-12 + - SAND-13 + - SAND-14 + - TOOL-01 + - TOOL-02 + +must_haves: + truths: + - "`nix build` succeeds and produces a claudebox binary" + - "claudebox launches and env inside sandbox contains only allowlisted vars" + - "Secret paths are invisible inside the sandbox" + - "DNS and SSL work (curl https succeeds)" + - "comma and nix shell can install packages" + - "Exit code passes through from claude to caller" + artifacts: [] + key_links: + - from: "nix build result" + to: "claudebox binary" + via: "result/bin/claudebox symlink" + pattern: "result/bin/claudebox" +--- + + +Build the claudebox flake and verify the sandbox works end-to-end through automated smoke tests and manual verification. + +Purpose: Confirm the sandbox actually isolates secrets, passes through tools, and runs Claude Code successfully. +Output: Verified working claudebox command. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/01-minimal-viable-sandbox/01-CONTEXT.md +@.planning/phases/01-minimal-viable-sandbox/01-01-SUMMARY.md +@flake.nix +@claudebox.sh + + + + + + Task 1: Build flake and run automated smoke tests + + + flake.nix + claudebox.sh + + +Run the following commands sequentially, fixing any issues that arise: + +**Step 1: Build the flake** +```bash +cd /home/toph/code/tools/claudebox +nix build +``` +If this fails, read the error and fix `flake.nix` or `claudebox.sh` as needed. Common issues: +- shellcheck errors in claudebox.sh (fix the shell code) +- Missing flake.lock (nix build will create it on first run) +- Package name mismatches (verify against nixpkgs) + +**Step 2: Verify the binary exists** +```bash +ls -la result/bin/claudebox +``` + +**Step 3: Run a minimal bwrap test without Claude** +To test the sandbox without needing Claude, run just the bwrap portion to verify mounts and env isolation. Extract the bwrap invocation concept and test key properties: + +```bash +# Test that the built script at least starts (will fail at claude lookup if claude not in PATH, that's ok) +# Instead, test bwrap directly using the same flags pattern: + +# Test 1: Verify --clearenv produces empty env +result/bin/claudebox 2>&1 || true +# If claude is found, it will launch. If not, we get the expected error. +``` + +Since claudebox requires `claude` in PATH and will exec into it, automated testing is limited. The key automated checks are: + +1. `nix build` succeeds (shellcheck passes, all deps resolve) +2. `result/bin/claudebox` exists and is executable +3. The script content in the Nix store passes basic sanity: `cat result/bin/claudebox` shows the wrapper with correct PATH setup + +Run: +```bash +# Check the built wrapper contains expected runtimeInputs in PATH +cat result/bin/claudebox | head -20 +``` + +If `nix build` fails due to shellcheck issues in claudebox.sh, fix them. Common shellcheck fixes: +- SC2086: Double-quote variable expansions +- SC2034: Unused variables (may need `# shellcheck disable=SC2034` if intentional) +- SC2155: Declare and assign separately + +After build succeeds, if `claude` is available on the host PATH, run a quick sandbox test: +```bash +# Quick test: launch claudebox with --help to verify it starts and exits cleanly +result/bin/claudebox --help 2>&1 | head -5 || true +``` +This should show Claude Code's help output if everything is wired correctly, or show a meaningful error. + + + test -x /home/toph/code/tools/claudebox/result/bin/claudebox && echo "PASS: binary exists" || echo "FAIL: binary missing" + + + - `nix build` exits 0 (no shellcheck errors, all deps resolve) + - `result/bin/claudebox` exists and is executable + - `flake.lock` exists (created by first build) + - The built wrapper script in the Nix store contains runtimeInputs PATH entries (visible in `cat result/bin/claudebox`) + + nix build succeeds and produces an executable claudebox binary + + + + Task 2: Manual sandbox verification + + Present the verification checklist below to the user and wait for their confirmation that each check passes. + Complete claudebox sandbox wrapping Claude Code with environment isolation, filesystem isolation, secret hiding, git support, and tool provisioning + +1. Launch claudebox from a project directory: + ``` + cd ~/some-project + /home/toph/code/tools/claudebox/result/bin/claudebox + ``` + +2. Inside the Claude session, verify environment isolation: + - Ask Claude to run `env | sort` -- should show ONLY allowlisted vars (HOME, PATH, TERM, USER, SHELL, TMPDIR, etc.) + - Confirm NO appearance of: SSH_AUTH_SOCK, AWS_PROFILE, GITHUB_TOKEN, or any secret vars + +3. Verify filesystem isolation: + - Ask Claude to run `ls ~/.ssh` -- should fail (directory not found) + - Ask Claude to run `ls ~/.gnupg` -- should fail + - Ask Claude to run `ls ~/.aws` -- should fail + - Ask Claude to run `ls ~/.claude` -- should succeed (mapped from ~/.claudebox) + +4. Verify tools work: + - Ask Claude to run `git status` -- should work in the project dir + - Ask Claude to run `curl -s https://example.com | head -5` -- should return HTML (DNS + SSL work) + - Ask Claude to run `, jq --help | head -3` -- should install and run jq via comma + - Ask Claude to run `rg --version` -- should show ripgrep version + +5. Exit Claude (Ctrl+C or /exit) and verify: + - The shell returns to your normal prompt + - `echo $?` shows the exit code from Claude (typically 0) + + + echo "CHECKPOINT: requires human verification" + + User confirms all sandbox isolation and tool provisioning checks pass + Type "approved" if all checks pass, or describe any issues found + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Build output -> Runtime | Nix build produces the sandbox script; verification confirms it behaves as designed | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-01-08 | Information Disclosure | Env leak in built binary | mitigate | Manual verification (Task 2 step 2) confirms only allowlisted vars appear in `env` output inside sandbox | +| T-01-09 | Information Disclosure | Secret path accessible | mitigate | Manual verification (Task 2 step 3) confirms ~/.ssh, ~/.gnupg, ~/.aws are not visible | + + + +1. `nix build` exits 0 +2. Human confirms env isolation (only allowlisted vars visible) +3. Human confirms filesystem isolation (secret paths invisible) +4. Human confirms tools work (git, curl, comma, ripgrep) +5. Human confirms clean exit behavior + + + +- claudebox builds from the Nix flake without errors +- Human verifies the sandbox isolates secrets and provides working tools +- Phase 1 success criteria from ROADMAP.md are met + + + +After completion, create `.planning/phases/01-minimal-viable-sandbox/01-02-SUMMARY.md` +