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