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