claudebox/.planning/phases/04-auth-passthrough/04-01-PLAN.md
2026-04-10 09:11:05 +00:00

211 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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>
## 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.
</threat_model>
## Tasks
<task id="4.1.1" name="Add credential file mount">
<read_first>
- claudebox.sh lines 284350 — the --dry-run display block and exec bwrap call; credential bind goes in both places
- claudebox.sh lines 98103 — CWD and ~/.claudebox setup pattern to follow
</read_first>
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).
<acceptance_criteria>
- [ ] `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
</acceptance_criteria>
</task>
<task id="4.1.2" name="Rewrite print_audit to unified list with Mounts and Network sections">
<read_first>
- claudebox.sh lines 171263 — 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 6576 — ANSI color variable definitions (BOLD, RESET, DIM, CYAN, YELLOW, GREEN, RED already defined)
- claudebox.sh lines 7891 — mask_value function signature and behavior; must be called for all env var values in the new display
</read_first>
Rewrite the `print_audit` function (lines 227263) 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.
<acceptance_criteria>
- [ ] `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)
</acceptance_criteria>
</task>
## 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`