docs(04): create phase 4 plan — credential mount and audit redesign
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
41bd51ed42
commit
40e40e3f30
2 changed files with 216 additions and 2 deletions
|
|
@ -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
|
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
|
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
|
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
|
### 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
|
**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 |
|
| 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 |
|
| 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 |
|
| 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 | - |
|
| 5. Per-Project Instance Isolation | v2.0 | 0/? | Not started | - |
|
||||||
| 6. Tiered Network Isolation | v2.0 | 0/? | Not started | - |
|
| 6. Tiered Network Isolation | v2.0 | 0/? | Not started | - |
|
||||||
| 7. Named Profiles | v2.0 | 0/? | Not started | - |
|
| 7. Named Profiles | v2.0 | 0/? | Not started | - |
|
||||||
|
|
|
||||||
211
.planning/phases/04-auth-passthrough/04-01-PLAN.md
Normal file
211
.planning/phases/04-auth-passthrough/04-01-PLAN.md
Normal file
|
|
@ -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>
|
||||||
|
## 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 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
|
||||||
|
</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 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
|
||||||
|
</read_first>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<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`
|
||||||
Loading…
Add table
Reference in a new issue