diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3e988d9..5c10d9b 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -37,7 +37,10 @@ Full details: [milestones/v1.0-ROADMAP.md](milestones/v1.0-ROADMAP.md) 1. Running claudebox with an active Claude subscription succeeds without re-authentication 2. OAuth token refresh completes silently — credentials file is updated and the session continues 3. When `ANTHROPIC_API_KEY` is set on the host, it is passed into the sandbox and takes precedence over OAuth -**Plans**: TBD +**Plans**: 1 plan + +Plans: +- [ ] 04-01-PLAN.md — Credential mount + audit redesign (unified env list, Mounts section, Network section) ### Phase 5: Per-Project Instance Isolation **Goal**: Each project directory has its own isolated Claude state so conversation history, todos, and settings do not bleed between projects @@ -81,7 +84,7 @@ Full details: [milestones/v1.0-ROADMAP.md](milestones/v1.0-ROADMAP.md) | 1. Minimal Viable Sandbox | v1.0 | 2/2 | Complete | 2026-04-09 | | 2. Env Audit and CLI Polish | v1.0 | 2/2 | Complete | 2026-04-09 | | 3. Sandbox-Aware Prompting | v1.0 | 1/1 | Complete | 2026-04-10 | -| 4. Auth Passthrough | v2.0 | 0/? | Not started | - | +| 4. Auth Passthrough | v2.0 | 0/1 | Not started | - | | 5. Per-Project Instance Isolation | v2.0 | 0/? | Not started | - | | 6. Tiered Network Isolation | v2.0 | 0/? | Not started | - | | 7. Named Profiles | v2.0 | 0/? | Not started | - | diff --git a/.planning/phases/04-auth-passthrough/04-01-PLAN.md b/.planning/phases/04-auth-passthrough/04-01-PLAN.md new file mode 100644 index 0000000..549b98d --- /dev/null +++ b/.planning/phases/04-auth-passthrough/04-01-PLAN.md @@ -0,0 +1,211 @@ +--- +plan: 1 +phase: 04 +wave: 1 +depends_on: [] +files_modified: + - claudebox.sh +requirements: [AUTH-01, AUTH-02] +autonomous: true +must_haves: + truths: + - "claudebox launches successfully when ~/.claude/.credentials.json exists on the host" + - "OAuth token refresh can write back to the credentials file (read-write mount)" + - "claudebox launches without error when ~/.claude/.credentials.json does not exist" + - "ANTHROPIC_API_KEY is passed into the sandbox when set on the host" + - "The audit screen shows all env vars in a single unified list with [~]/[>]/[+] prefixes" + - "The audit screen shows a Mounts section and a Network section after the env list" + - "The --dry-run output mirrors the credential bind when the file exists" + artifacts: + - path: "claudebox.sh" + provides: "Credential mount logic, updated print_audit, updated --dry-run block" + contains: "credentials.json" + key_links: + - from: "claudebox.sh credential bind block" + to: "exec bwrap call (line ~327)" + via: "--bind $HOME/.claude/.credentials.json $HOME/.claude/.credentials.json" + - from: "claudebox.sh credential bind block" + to: "--dry-run display block (line ~292)" + via: "mirrored echo of credential bind" + - from: "print_audit" + to: "AUDIT_SANDBOX_KEYS / AUDIT_HOST_KEYS / AUDIT_EXTRA_KEYS arrays" + via: "[~]/[>]/[+] prefix loop" +--- + +# Plan 1: Credential Mount + Audit Redesign + +## Goal + +Mount `~/.claude/.credentials.json` read-write into the sandbox and rewrite the pre-launch audit to a unified env/mounts/network display. + +## Context + +Claude Code stores OAuth tokens in `~/.claude/.credentials.json`. Without mounting this file, users must re-authenticate every time. The mount must be read-write because OAuth refresh writes new tokens back to the file. When `ANTHROPIC_API_KEY` is set on the host, it is already passed through (line 204 of claudebox.sh) — no changes needed for that path. + +The current audit shows three separate sections (Sandbox-generated / Host allowlisted / Extra). The redesign collapses these into one list with `[~]`/`[>]`/`[+]` prefixes (accessible without color), adds a Mounts section showing active filesystem binds, and adds a Network section (placeholder showing "full (host network)" until Phase 6). + + +## Threat Model + +**Assets:** +- `~/.claude/.credentials.json` — OAuth tokens granting access to the user's Claude subscription +- `ANTHROPIC_API_KEY` — API key granting billable Claude API access + +**Threat actors:** +- Malicious code running inside the sandbox that could exfiltrate credentials via network or file writes +- A compromised Claude Code session writing a crafted `.credentials.json` back to the host + +**Attack vectors:** +- Sandbox code reads `.credentials.json` and exfiltrates tokens over the network (full host network in Phase 4) +- Sandbox code overwrites `.credentials.json` with attacker-controlled content, poisoning the host credential store +- Path traversal: if the bind target were `$HOME/.claude/` (directory), sandbox code could write new files into `~/.claude` on the host + +**Mitigations:** +- Bind is file-scoped (`--bind .credentials.json .credentials.json`), NOT directory-scoped — sandbox cannot create new files in the host `~/.claude` directory via this mount +- Read-write is required for legitimate OAuth refresh; this is an accepted trade-off documented in D-02 +- `ANTHROPIC_API_KEY` is masked in audit output by `mask_value` (already implemented) +- Phase 6 network isolation will allow users to restrict exfiltration surface; Phase 4 accepts full-network risk consistent with existing behavior + +**Residual risk:** +- A malicious prompt could cause Claude to read and exfiltrate the OAuth token over the network. Accepted: this is equivalent risk to the API key already in the sandbox. Phase 6 network tiers reduce this surface. +- OAuth token can be overwritten by sandbox code. Accepted: the file must be writable for refresh to work (D-02). The host file is the user's own credential file. + + +## Tasks + + + + +- claudebox.sh lines 284–350 — the --dry-run display block and exec bwrap call; credential bind goes in both places +- claudebox.sh lines 98–103 — CWD and ~/.claudebox setup pattern to follow + + +After the `mkdir -p "$HOME/.claudebox"` call and before the `print_audit` invocation, add a block that: + +1. Sets `CREDS_FILE="$HOME/.claude/.credentials.json"` +2. Checks `if [[ -f "$CREDS_FILE" ]]; then` sets `CREDS_MOUNT=true`; else `CREDS_MOUNT=false` +3. Adds `--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json"` to the exec bwrap call (after the `--symlink "$HOME/.claudebox" "$HOME/.claude"` line) when `CREDS_MOUNT=true` +4. Mirrors the same conditional bind in the `--dry-run` display block (after the symlink echo) + +**Bwrap mount ordering note:** The credential file bind must come AFTER the `--symlink "$HOME/.claudebox" "$HOME/.claude"` line. bwrap processes mounts in order; the symlink creates `~/.claude → ~/.claudebox`, and the subsequent file bind layers `.credentials.json` on top of it inside the sandbox. This matches the established pattern from the code context. + +Do NOT use `--ro-bind` — the mount must be read-write for OAuth token refresh (D-02). + +Do NOT add any warning or error output when the file is absent — skip silently (D-03). + +Always add the bind when the file exists, even if `ANTHROPIC_API_KEY` is also set (D-04). + + +- [ ] `grep -n 'CREDS_FILE' claudebox.sh` shows the variable declared as `CREDS_FILE="$HOME/.claude/.credentials.json"` +- [ ] `grep -n 'CREDS_MOUNT' claudebox.sh` shows both the conditional set and usage in bwrap exec +- [ ] `grep -n 'credentials.json' claudebox.sh` shows the bind appears in BOTH the exec bwrap block AND the --dry-run block +- [ ] The bind uses `--bind` (not `--ro-bind`) — `grep 'ro-bind.*credentials' claudebox.sh` returns nothing +- [ ] `grep -A2 'CREDS_MOUNT' claudebox.sh` shows the condition is `[[ -f "$CREDS_FILE" ]]` — file existence check, not variable existence +- [ ] No `echo` or `>&2` output is produced when the credentials file is absent — the skip is completely silent + + + + + + + +- claudebox.sh lines 171–263 — the audit data structures (AUDIT_SANDBOX_KEYS, AUDIT_HOST_KEYS, AUDIT_EXTRA_KEYS arrays and associated maps) and the current print_audit function; the rewrite must consume the same data structures +- claudebox.sh lines 65–76 — ANSI color variable definitions (BOLD, RESET, DIM, CYAN, YELLOW, GREEN, RED already defined) +- claudebox.sh lines 78–91 — mask_value function signature and behavior; must be called for all env var values in the new display + + +Rewrite the `print_audit` function (lines 227–263) to produce the new unified format per D-06, D-07, D-08, D-09, D-10. + +**New print_audit structure:** + +``` +=== Sandbox Environment === + + [~] HOME=/home/user ← green [~] prefix, sandbox-generated + [~] USER=user + [~] PATH= ← PATH gets special multiline treatment (same as before) + /nix/store/.../bin + [>] TERM=xterm-256color ← yellow [>] prefix, host allowlisted (only if set) + [>] ANTHROPIC_API_KEY=abc123...xyz ← masked by mask_value + [+] MY_VAR=value ← cyan [+] prefix, extra via CLAUDEBOX_EXTRA_ENV + +Mounts: + CWD /path/to/project (read-write) + ~/.claude ~/.claudebox (read-write) + credentials ~/.claude/.credentials.json (read-write) ← only if CREDS_MOUNT=true + +Network: + full (host network) +``` + +**Implementation rules:** + +1. Single loop over all three key arrays in order: sandbox keys first ([~]), then host keys ([>]), then extra keys ([+]) +2. Each line: ` {COLOR}{PREFIX}{RESET} {var}={masked_value}` — the prefix `[~]`, `[>]`, `[+]` is printed in color AND must be readable without color (accessibility per D-07) +3. PATH gets the same multiline indented treatment as the current implementation — show label then each path entry indented +4. After the env list, print a blank line then `${BOLD}Mounts:${RESET}` section: + - Always show: ` CWD $CWD (read-write)` + - Always show: ` ~/.claude $HOME/.claudebox (read-write)` + - Conditionally show (when `CREDS_MOUNT=true`): ` credentials $HOME/.claude/.credentials.json (read-write)` + - Use consistent column alignment — pad the label to a fixed width (e.g., 12 chars) so values align +5. After Mounts, print a blank line then `${BOLD}Network:${RESET}` section: + - Always show: ` full (host network)` + - This is a Phase 4 placeholder — no dynamic logic +6. All output to stderr (`>&2`) +7. Unset allowlisted host vars are silently omitted from the display (discretion decision from CONTEXT.md) + +The existing `AUDIT_SANDBOX_KEYS`, `AUDIT_SANDBOX_VALS`, `AUDIT_HOST_KEYS`, `AUDIT_HOST_VALS`, `AUDIT_EXTRA_KEYS`, `AUDIT_EXTRA_VALS` arrays must NOT be restructured — only `print_audit` changes. + + +- [ ] `grep '\[~\]' claudebox.sh` matches inside print_audit, used for sandbox-generated vars +- [ ] `grep '\[>\]' claudebox.sh` matches inside print_audit, used for host-allowlisted vars +- [ ] `grep '\[+\]' claudebox.sh` matches inside print_audit, used for extra vars +- [ ] `grep 'Mounts:' claudebox.sh` matches inside print_audit +- [ ] `grep 'Network:' claudebox.sh` matches inside print_audit +- [ ] `grep 'full (host network)' claudebox.sh` matches inside print_audit +- [ ] `grep 'CREDS_MOUNT' claudebox.sh` shows the Mounts section conditionally includes the credentials line +- [ ] `grep 'mask_value' claudebox.sh` shows mask_value is called for every env var value in the new loops (not just some vars) +- [ ] `grep '>&2' claudebox.sh` — all print_audit echo/printf calls redirect to stderr +- [ ] `bash -n claudebox.sh` exits 0 (no syntax errors) + + + + +## Verification + +```bash +# Syntax check +bash -n claudebox.sh + +# Verify credential bind present in both exec and dry-run blocks +grep -n 'credentials.json' claudebox.sh + +# Verify read-write (not ro-bind) for credentials +grep 'ro-bind.*credentials' claudebox.sh # must return nothing + +# Verify prefix scheme present +grep -E '\[~\]|\[>\]|\[+\]' claudebox.sh + +# Verify Mounts and Network sections +grep -E 'Mounts:|Network:|full .host network.' claudebox.sh + +# Functional test: dry-run with credentials file present +# (Create a test file, run dry-run, confirm bind appears in output) +touch /tmp/test_credentials.json +HOME_ORIG="$HOME" +# If test harness available: claudebox --dry-run should show --bind ...credentials.json +``` + +## Must-Haves + +- `~/.claude/.credentials.json` is mounted read-write into the sandbox when it exists on the host +- When the file does not exist, claudebox starts without any error or warning +- The exec bwrap call and the --dry-run display block are in sync (credential bind appears in both or neither) +- The audit screen shows `[~]`, `[>]`, and `[+]` prefixes that distinguish env var categories without relying on color alone +- The audit screen shows a Mounts section listing CWD, ~/.claude, and (conditionally) the credentials file +- The audit screen shows a Network section with "full (host network)" as a Phase 6 placeholder +- `bash -n claudebox.sh` passes (no syntax errors introduced) + +## Output + +After completion, create `.planning/phases/04-auth-passthrough/04-01-SUMMARY.md`