--- 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`