docs(01): create phase plan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Mühl 2026-04-09 11:02:11 +02:00
parent da29430168
commit 71790d714b
No known key found for this signature in database
GPG key ID: 925AC7D69955293F
3 changed files with 592 additions and 5 deletions

View file

@ -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 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 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 5. Ctrl+C terminates the session cleanly; exit code from Claude passes through to the caller
**Plans**: TBD **Plans:** 2 plans
Plans: Plans:
- [ ] 01-01: TBD - [ ] 01-01-PLAN.md -- Create flake.nix and claudebox.sh with complete bwrap sandbox
- [ ] 01-02: TBD - [ ] 01-02-PLAN.md -- Build verification and manual sandbox smoke test
- [ ] 01-03: TBD
### Phase 2: Env Audit and CLI Polish ### Phase 2: Env Audit and CLI Polish
**Goal**: User can review exactly what enters the sandbox before launch, and has diagnostic tools for troubleshooting **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 | | 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 | - | | 2. Env Audit and CLI Polish | 0/1 | Not started | - |
| 3. Sandbox-Aware Prompting | 0/1 | Not started | - | | 3. Sandbox-Aware Prompting | 0/1 | Not started | - |

View file

@ -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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Create flake.nix with all inputs and writeShellApplication</name>
<files>flake.nix</files>
<read_first>
.planning/phases/01-minimal-viable-sandbox/01-RESEARCH.md
</read_first>
<action>
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).
</action>
<verify>
<automated>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"</automated>
</verify>
<acceptance_criteria>
- 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`
</acceptance_criteria>
<done>flake.nix exists with correct flake structure, all runtime dependencies, and readFile of claudebox.sh</done>
</task>
<task type="auto">
<name>Task 2: Create claudebox.sh with complete bwrap sandbox logic</name>
<files>claudebox.sh</files>
<read_first>
.planning/phases/01-minimal-viable-sandbox/01-RESEARCH.md
.planning/phases/01-minimal-viable-sandbox/01-CONTEXT.md
</read_first>
<action>
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" <<GITEOF
[user]
name = $GIT_NAME
email = $GIT_EMAIL
[safe]
directory = *
GITEOF
```
**Section 6: Build environment --setenv args array (per D-03, D-04, SAND-02, SAND-03)**
Sandbox-generated vars (D-04) are set directly, never from host:
```bash
ENV_ARGS=(
--setenv HOME "$HOME"
--setenv USER "$USER"
--setenv PATH "$SANDBOX_PATH"
--setenv SHELL /bin/bash
--setenv TMPDIR /tmp
--setenv XDG_RUNTIME_DIR /tmp
--setenv NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
--setenv SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
)
```
Allowlisted host vars -- only pass if set on host:
```bash
HOST_ALLOWLIST=(TERM EDITOR LANG LC_ALL ANTHROPIC_API_KEY)
for var in "${HOST_ALLOWLIST[@]}"; do
if [[ -v "$var" ]]; then
ENV_ARGS+=(--setenv "$var" "${!var}")
fi
done
```
CLAUDEBOX_EXTRA_ENV escape hatch (per D-03, comma-separated):
```bash
if [[ -v CLAUDEBOX_EXTRA_ENV ]]; then
IFS=',' read -ra EXTRAS <<< "$CLAUDEBOX_EXTRA_ENV"
for var in "${EXTRAS[@]}"; do
var="${var// /}" # trim whitespace
if [[ -n "$var" ]] && [[ -v "$var" ]]; then
ENV_ARGS+=(--setenv "$var" "${!var}")
fi
done
fi
```
**Section 7: exec bwrap (per SAND-04 through SAND-15, UX-06, D-01)**
```bash
exec bwrap \
--clearenv \
"${ENV_ARGS[@]}" \
--tmpfs / \
--proc /proc \
--dev /dev \
--tmpfs /tmp \
--ro-bind /nix/store /nix/store \
--bind /nix/var/nix /nix/var/nix \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--ro-bind /etc/ssl /etc/ssl \
--ro-bind /etc/static /etc/static \
--ro-bind /etc/passwd /etc/passwd \
--ro-bind /etc/group /etc/group \
--ro-bind /etc/hosts /etc/hosts \
--ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \
--ro-bind /etc/nix /etc/nix \
--symlink "$(command -v env)" /usr/bin/env \
--tmpfs "$HOME" \
--bind "$HOME/.claudebox" "$HOME/.claude" \
--ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig" \
--bind "$CWD" "$CWD" \
--chdir "$CWD" \
-- "$CLAUDE_BIN" --dangerously-skip-permissions "$@"
```
Mount ordering rationale (most general to most specific):
1. `--tmpfs /` -- empty root (SAND-04)
2. `/proc`, `/dev`, `/tmp` -- system essentials (SAND-11)
3. `/nix/store` ro, `/nix/var/nix` rw -- Nix access (SAND-06, SAND-07, TOOL-03)
4. `/etc/*` -- DNS, SSL, user resolution, nix config (SAND-12, SAND-13)
5. `/usr/bin/env` symlink -- shebang support (Pitfall 8)
6. `$HOME` tmpfs -- clean home dir
7. `~/.claudebox` as `~/.claude` -- Claude config (SAND-08)
8. `.gitconfig` -- git identity (GIT-01, GIT-02)
9. `$CWD` -- project directory (SAND-05), most specific = last
Per D-01: all args after claudebox's own flags pass through via `"$@"`. Phase 1 has no claudebox-specific flags (--yes/--dry-run/--check are Phase 2), so ALL args pass through.
Per UX-06: `--dangerously-skip-permissions` is always injected before `"$@"`.
Per SAND-14/SAND-15: `exec` ensures no intermediate shell -- signals propagate, exit code passes through.
Per SAND-09: Secret paths are never mounted. The tmpfs root and tmpfs HOME ensure nothing leaks. Only explicit bind-mounts above are visible.
IMPORTANT: Do NOT add `#!/bin/bash` or `set -euo pipefail` -- `writeShellApplication` adds these automatically.
</action>
<verify>
<automated>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"</automated>
</verify>
<acceptance_criteria>
- 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
</acceptance_criteria>
<done>claudebox.sh exists with complete bwrap invocation covering all SAND-*, TOOL-*, GIT-*, and UX-06 requirements</done>
</task>
</tasks>
<threat_model>
## 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. |
</threat_model>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/01-minimal-viable-sandbox/01-01-SUMMARY.md`
</output>

View file

@ -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"
---
<objective>
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.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.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
</context>
<tasks>
<task type="auto">
<name>Task 1: Build flake and run automated smoke tests</name>
<files></files>
<read_first>
flake.nix
claudebox.sh
</read_first>
<action>
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.
</action>
<verify>
<automated>test -x /home/toph/code/tools/claudebox/result/bin/claudebox && echo "PASS: binary exists" || echo "FAIL: binary missing"</automated>
</verify>
<acceptance_criteria>
- `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`)
</acceptance_criteria>
<done>nix build succeeds and produces an executable claudebox binary</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2: Manual sandbox verification</name>
<files></files>
<action>Present the verification checklist below to the user and wait for their confirmation that each check passes.</action>
<what-built>Complete claudebox sandbox wrapping Claude Code with environment isolation, filesystem isolation, secret hiding, git support, and tool provisioning</what-built>
<how-to-verify>
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)
</how-to-verify>
<verify>
<automated>echo "CHECKPOINT: requires human verification"</automated>
</verify>
<done>User confirms all sandbox isolation and tool provisioning checks pass</done>
<resume-signal>Type "approved" if all checks pass, or describe any issues found</resume-signal>
</task>
</tasks>
<threat_model>
## 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 |
</threat_model>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.planning/phases/01-minimal-viable-sandbox/01-02-SUMMARY.md`
</output>