docs(01): create phase plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
da29430168
commit
71790d714b
3 changed files with 592 additions and 5 deletions
|
|
@ -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 | - |
|
||||
|
|
|
|||
380
.planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md
Normal file
380
.planning/phases/01-minimal-viable-sandbox/01-01-PLAN.md
Normal 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>
|
||||
208
.planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md
Normal file
208
.planning/phases/01-minimal-viable-sandbox/01-02-PLAN.md
Normal 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>
|
||||
Loading…
Add table
Reference in a new issue