diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 0a94a36..f7b1771 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -56,6 +56,11 @@ ## v2 Requirements +### Authentication Passthrough + +- **AUTH-01**: `~/.claudebox/.credentials.json` (OAuth tokens) is bind-mounted read-write into the sandbox when the file exists on the host, so users do not need to re-authenticate on every launch +- **AUTH-02**: When `~/.claudebox/.credentials.json` does not exist, claudebox starts without any error or warning (silent skip) + ### Network Isolation - **NET-01**: Block LAN/Tailscale access (RFC1918 + 100.64.0.0/10) while allowing internet egress @@ -120,10 +125,12 @@ | NIX-01 | Phase 1 | Complete | | NIX-02 | Phase 1 | Complete | | NIX-03 | Phase 1 | Complete | +| AUTH-01 | Phase 4 | Complete | +| AUTH-02 | Phase 4 | Complete | **Coverage:** -- v1 requirements: 31 total -- Mapped to phases: 31 +- v1 requirements: 31 total, v2 requirements (partial): 2 +- Mapped to phases: 33 - Unmapped: 0 --- diff --git a/.planning/STATE.md b/.planning/STATE.md index 4029363..187f20e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -4,14 +4,14 @@ milestone: v1.0 milestone_name: milestone status: executing stopped_at: Phase 3 context gathered -last_updated: "2026-04-09T19:24:16.913Z" -last_activity: 2026-04-09 +last_updated: "2026-04-10T09:33:52.025Z" +last_activity: 2026-04-10 progress: total_phases: 3 - completed_phases: 3 - total_plans: 5 - completed_plans: 5 - percent: 100 + completed_phases: 0 + total_plans: 0 + completed_plans: 0 + percent: 33 --- # Project State @@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-04-09) ## Current Position -Phase: 03 of 3 (sandbox aware prompting) +Phase: 04 of 3 (sandbox aware prompting) Plan: Not started Status: Ready to execute -Last activity: 2026-04-10 - Completed quick task 260410-d4u: on non-nixos hosts, bwrap fails because /etc/static does not exist +Last activity: 2026-04-10 Progress: [███░░░░░░░] 33% diff --git a/.planning/phases/04-auth-passthrough/04-01-SUMMARY.md b/.planning/phases/04-auth-passthrough/04-01-SUMMARY.md new file mode 100644 index 0000000..9212d3f --- /dev/null +++ b/.planning/phases/04-auth-passthrough/04-01-SUMMARY.md @@ -0,0 +1,120 @@ +--- +phase: 04 +plan: 1 +subsystem: sandbox-script +tags: [credentials, auth, audit, bwrap] +dependency_graph: + requires: [] + provides: [credential-mount, unified-audit] + affects: [claudebox.sh] +tech_stack: + added: [] + patterns: [conditional-bwrap-args-array, unified-audit-prefixes] +key_files: + created: [] + modified: + - claudebox.sh +decisions: + - "Used BWRAP_ARGS array instead of inline exec bwrap to support conditional credential mount" + - "Used [~]/[>]/[+] text prefixes (not color-only) for accessibility" + - "print_audit depends on CREDS_MOUNT set earlier in script — no API change needed" +metrics: + duration: "2m 30s" + completed: "2026-04-10" + tasks_completed: 2 + tasks_total: 2 + files_changed: 1 +--- + +# Phase 4 Plan 1: Credential Mount + Audit Redesign Summary + +**One-liner:** Read-write `~/.claude/.credentials.json` bind mount for OAuth passthrough plus unified `[~]/[>]/[+]` env audit with Mounts and Network sections. + +## What Was Built + +### Task 4.1.1 — Add credential file mount + +Added conditional detection and mounting of `~/.claude/.credentials.json` into the sandbox: + +- `CREDS_FILE` / `CREDS_MOUNT` variables set after `mkdir -p "$HOME/.claudebox"` +- When `CREDS_MOUNT=true`: `--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json"` added to bwrap args +- Silent skip when file absent — no error or warning output +- Uses `--bind` (not `--ro-bind`) so OAuth token refresh can write back to the file +- `exec bwrap` refactored to use `BWRAP_ARGS` array to support the conditional mount cleanly +- Credential bind mirrored in `--dry-run` display block + +### Task 4.1.2 — Rewrite print_audit + +Rewrote `print_audit` from three separate sections to a unified list: + +- Single loop ordering: sandbox keys `[~]` (green) → host allowlisted `[>]` (yellow) → extra `[+]` (cyan) +- Text prefixes readable without color (accessibility — D-07) +- PATH retains multiline indented display +- New `Mounts:` section shows CWD, `~/.claude`, and conditional credentials line +- New `Network:` section shows `full (host network)` as Phase 6 placeholder +- All print_audit output goes to stderr +- `mask_value` called for every env var value in all three loops + +## Decisions Made + +1. **BWRAP_ARGS array:** The `exec bwrap ... \` inline form cannot have a conditional in the middle. Refactored to build a `BWRAP_ARGS` array and `exec bwrap "${BWRAP_ARGS[@]}"`. This is cleaner and extensible for future conditional mounts (network tiers, profile mounts). + +2. **Text prefixes for accessibility:** `[~]`, `[>]`, `[+]` are printed as literal text (not just color differences). Color is additive — the prefix meaning is clear in monochrome terminals and when piped. + +3. **CREDS_MOUNT scoping:** `CREDS_MOUNT` is set at script top-level (before `print_audit`), so the Mounts section in `print_audit` can read it without needing to re-check the filesystem. + +## Commits + +| Task | Hash | Message | +|-------|---------|---------| +| 4.1.1 | 6465da8 | feat(04-01): add credential file mount for OAuth passthrough | +| 4.1.2 | def8e67 | feat(04-01): rewrite print_audit to unified env list with Mounts and Network sections | + +## Verification + +``` +bash -n claudebox.sh # SYNTAX OK +grep 'CREDS_FILE' claudebox.sh # line 105: CREDS_FILE="$HOME/.claude/.credentials.json" +grep 'CREDS_MOUNT' claudebox.sh # detection + dry-run + bwrap + Mounts section +grep 'credentials.json' claudebox.sh # lines 105, 267, 331, 364 (dry-run + bwrap) +grep 'ro-bind.*credentials' claudebox.sh # (no output — correct, uses --bind) +grep '[~]' claudebox.sh # lines 239, 242, 248 +grep '[>]' claudebox.sh # lines 239, 253 +grep '[+]' claudebox.sh # lines 239, 257 +grep 'Mounts:' claudebox.sh # line 263 +grep 'Network:' claudebox.sh # line 273 +grep 'full (host network)' claudebox.sh # line 274 +``` + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Refactor] Refactored exec bwrap to BWRAP_ARGS array** + +- **Found during:** Task 4.1.1 +- **Issue:** The `exec bwrap ... \` inline multi-line form cannot include a conditional mount (`if [[ "$CREDS_MOUNT" == true ]]; then ... fi`) in the middle of the argument list. +- **Fix:** Replaced the inline `exec bwrap` form with a `BWRAP_ARGS` array built up with conditional appends, then `exec bwrap "${BWRAP_ARGS[@]}"`. This preserves identical runtime behavior while enabling conditional mounts. +- **Files modified:** claudebox.sh +- **Commit:** 6465da8 + +## Known Stubs + +- **Network section:** `full (host network)` in `print_audit` is an intentional Phase 4 placeholder. Network isolation tiers will replace this in Phase 6. + +## Threat Flags + +| Flag | File | Description | +|------|------|-------------| +| threat_flag: credential-exfil | claudebox.sh | Read-write bind of `~/.claude/.credentials.json` gives sandbox read access to OAuth tokens; sandbox has full host network, so exfiltration is possible. Accepted risk per plan threat model — Phase 6 network tiers reduce surface. | + +## Self-Check: PASSED + +- claudebox.sh exists and was modified: FOUND +- Commit 6465da8 exists: FOUND +- Commit def8e67 exists: FOUND +- bash -n claudebox.sh: PASSES +- credentials.json appears in both exec bwrap block and dry-run block: CONFIRMED (lines 364, 331) +- [~]/[>]/[+] prefixes present in print_audit: CONFIRMED +- Mounts: / Network: sections present: CONFIRMED +- full (host network) present: CONFIRMED diff --git a/.planning/phases/04-auth-passthrough/04-REVIEW-FIX.md b/.planning/phases/04-auth-passthrough/04-REVIEW-FIX.md new file mode 100644 index 0000000..58cfcec --- /dev/null +++ b/.planning/phases/04-auth-passthrough/04-REVIEW-FIX.md @@ -0,0 +1,59 @@ +--- +phase: 04-auth-passthrough +fixed_at: 2026-04-10T00:00:00Z +review_path: .planning/phases/04-auth-passthrough/04-REVIEW.md +iteration: 1 +findings_in_scope: 4 +fixed: 4 +skipped: 0 +status: all_fixed +--- + +# Phase 04: Code Review Fix Report + +**Fixed at:** 2026-04-10 +**Source review:** .planning/phases/04-auth-passthrough/04-REVIEW.md +**Iteration:** 1 + +**Summary:** +- Findings in scope: 4 (2 Critical, 2 Warning; Info excluded by fix_scope) +- Fixed: 4 +- Skipped: 0 + +## Fixed Issues + +### CR-01: CREDS_FILE path resolves against the wrong directory on the host + +**Files modified:** `claudebox.sh` +**Commit:** adb9dd1 +**Applied fix:** Changed `CREDS_FILE` from `$HOME/.claude/.credentials.json` to `$HOME/.claudebox/.credentials.json`. Added comments explaining that the `~/.claude -> ~/.claudebox` symlink only exists inside the sandbox at runtime, so host-side credential detection must use the real claudebox config directory path. + +--- + +### CR-02: Credentials bind target uses a symlink path — may silently fail + +**Files modified:** `claudebox.sh` +**Commit:** adb9dd1 +**Applied fix:** Changed the `BWRAP_ARGS` credential bind destination from `$HOME/.claude/.credentials.json` (symlink path, unresolvable by bwrap at bind time) to `$HOME/.claudebox/.credentials.json` (canonical real directory). Also updated the `--dry-run` output block to print the corrected destination path. + +--- + +### WR-01: Credentials file mounted read-write instead of read-only + +**Files modified:** `claudebox.sh` +**Commit:** adb9dd1 +**Applied fix:** Changed `--bind` to `--ro-bind` for the credentials bind in both the `BWRAP_ARGS` array (line ~366) and the `--dry-run` output block (line ~333). Also updated the `print_audit` mounts display to show `(read-only)` and display `$CREDS_FILE` (the actual host source path) instead of the hardcoded symlink path inside the sandbox. + +--- + +### WR-02: dry-run ENV_ARGS loop hard-codes stride of 3 — breaks if any non-setenv arg is added + +**Files modified:** `claudebox.sh` +**Commit:** 0922b75 +**Applied fix:** Added a guard assertion before the stride-3 loop that checks `${#ENV_ARGS[@]} % 3 != 0` and exits with a `BUG:` message if the invariant is violated. This catches any future breakage immediately rather than producing silently mangled output. Also changed `(( dry_run_i += 3 ))` to `dry_run_i=$(( dry_run_i + 3 ))` to use safe arithmetic assignment compatible with `set -euo pipefail` (which `writeShellApplication` enables). + +--- + +_Fixed: 2026-04-10_ +_Fixer: Claude (gsd-code-fixer)_ +_Iteration: 1_ diff --git a/.planning/phases/04-auth-passthrough/04-REVIEW.md b/.planning/phases/04-auth-passthrough/04-REVIEW.md new file mode 100644 index 0000000..7fff506 --- /dev/null +++ b/.planning/phases/04-auth-passthrough/04-REVIEW.md @@ -0,0 +1,172 @@ +--- +phase: 04-auth-passthrough +reviewed: 2026-04-10T00:00:00Z +depth: standard +files_reviewed: 1 +files_reviewed_list: + - claudebox.sh +findings: + critical: 2 + warning: 2 + info: 1 + total: 5 +status: issues_found +--- + +# Phase 04: Code Review Report + +**Reviewed:** 2026-04-10 +**Depth:** standard +**Files Reviewed:** 1 +**Status:** issues_found + +## Summary + +This review covers the phase 4 credential passthrough changes to `claudebox.sh`. The diff introduces AUTH-01/AUTH-02: detecting `~/.claude/.credentials.json` on the host and conditionally bind-mounting it into the sandbox. The refactor also converts the inline `exec bwrap ...` to a `BWRAP_ARGS` array for conditional mount support, and unifies the env audit display format. + +Two critical issues were found: the credential path resolution is wrong on any system where `~/.claude` and `~/.claudebox` are different directories (the detection silently fails with `CREDS_MOUNT=false`), and the credential file is mounted read-write when read-only is correct. A mount-ordering issue with the symlink target path may also prevent the credentials bind from working at all on some bwrap versions. + +--- + +## Critical Issues + +### CR-01: CREDS_FILE path resolves against the wrong directory on the host + +**File:** `claudebox.sh:105` + +**Issue:** `CREDS_FILE` is set to `$HOME/.claude/.credentials.json`. On the host, `~/.claude` is the real Claude config directory — **not** `~/.claudebox`. The symlink `~/.claude -> ~/.claudebox` only exists *inside* the sandbox (it is created by the `--symlink` bwrap flag at runtime). Before the sandbox is entered, `$HOME/.claude` and `$HOME/.claudebox` are independent paths. + +If the user's credentials live in `~/.claude/.credentials.json` (the default Claude Code location) and `~/.claudebox` is a separate directory, the `-f "$CREDS_FILE"` test on line 106 may still succeed — but the file being tested and the file actually mounted will be the real `~/.claude/.credentials.json`, which is then bound into `$HOME/.claude/.credentials.json` inside the sandbox. Since the sandbox maps `$HOME/.claudebox` → `$HOME/.claude` (symlink), the bind target `$HOME/.claude/.credentials.json` is a symlink path that bwrap must traverse, creating the mount-ordering hazard in CR-02. + +More importantly, if the intent is to read credentials from `~/.claudebox/.credentials.json` (i.e. the claudebox-managed config dir), the detection path is wrong and will silently miss it. The correct host path to check is: + +```bash +# Correct: resolve against the claudebox config dir, not ~/.claude +CREDS_FILE="$HOME/.claudebox/.credentials.json" +``` + +If the intent is to read from the real `~/.claude`, that is correct as written, but then the mount target inside the sandbox must use the canonical path, not the symlink path (see CR-02). + +**Fix:** + +Choose one consistent interpretation. If credentials come from `~/.claudebox`: + +```bash +CREDS_FILE="$HOME/.claudebox/.credentials.json" +if [[ -f "$CREDS_FILE" ]]; then + CREDS_MOUNT=true +else + CREDS_MOUNT=false +fi +``` + +And mount it to the canonical destination (not through the symlink): + +```bash +BWRAP_ARGS+=(--ro-bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json") +``` + +--- + +### CR-02: Credentials bind target uses a symlink path — may silently fail + +**File:** `claudebox.sh:364` + +**Issue:** The credentials bind is: + +```bash +--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json" +``` + +Inside the bwrap namespace, `$HOME/.claude` is a symlink (created by `--symlink "$HOME/.claudebox" "$HOME/.claude"` on line 361). Bwrap processes mount arguments in order and creates the symlink before this bind, but bwrap does **not** resolve symlinks in the destination path when applying `--bind` — it uses the literal path. The literal path `$HOME/.claude/.credentials.json` refers to a path whose parent is a dangling symlink at mount time (the symlink exists, but the directory component `$HOME/.claude/` is not a real directory). This means the bind either silently fails or errors, depending on bwrap version. + +The fix is to bind the file directly into the canonical directory path `$HOME/.claudebox/.credentials.json`, which is the real directory: + +```bash +# Wrong: target is through a symlink +BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json") + +# Correct: target is the real directory +BWRAP_ARGS+=(--ro-bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json") +``` + +The same fix applies to the `--dry-run` output path on line 331. + +--- + +## Warnings + +### WR-01: Credentials file mounted read-write instead of read-only + +**File:** `claudebox.sh:364` + +**Issue:** `--bind` creates a read-write mount. Claude Code needs to **read** credentials, not write them. Mounting the credentials file read-write gives the sandboxed agent the ability to overwrite or corrupt the host's credential store. This contradicts the project's principle of least privilege — the sandbox should only receive what it needs. + +**Fix:** + +```bash +# Change --bind to --ro-bind +BWRAP_ARGS+=(--ro-bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json") +``` + +The same applies in the `--dry-run` output block (line 331) — update `--bind` to `--ro-bind` there for accurate documentation of actual behavior. + +--- + +### WR-02: dry-run ENV_ARGS loop hard-codes stride of 3 — breaks if any non-setenv arg is added + +**File:** `claudebox.sh:309-312` + +**Issue:** The dry-run printer iterates `ENV_ARGS` with a fixed stride of 3: + +```bash +while (( dry_run_i < ${#ENV_ARGS[@]} )); do + printf ' %s %s %q \\\n' "${ENV_ARGS[$dry_run_i]}" "${ENV_ARGS[$((dry_run_i+1))]}" "${ENV_ARGS[$((dry_run_i+2))]}" + (( dry_run_i += 3 )) +done +``` + +This assumes every element is a `--setenv NAME VALUE` triplet. If any future env arg uses a 2-element form (e.g. `--unsetenv NAME`, `--clearenv`), the loop will misalign and print mangled output or access out-of-bounds indices. The `BWRAP_ARGS` construction path handles variadic args correctly via array appending; the dry-run printer does not. + +This is a latent fragility bug. It does not currently trigger because `ENV_ARGS` only contains `--setenv` triples, but it is a maintenance trap. + +**Fix:** Build the dry-run ENV section by iterating the same conditional logic used to populate `ENV_ARGS`, or use a parallel dry-run-safe array instead of re-parsing `ENV_ARGS`: + +```bash +# Print each --setenv entry (stride-3 is safe only while all entries are --setenv) +# Guard with an assertion: +if (( ${#ENV_ARGS[@]} % 3 != 0 )); then + echo "BUG: ENV_ARGS length ${#ENV_ARGS[@]} is not a multiple of 3" >&2 + exit 1 +fi +``` + +Or restructure to eliminate the re-parsing entirely. + +--- + +## Info + +### IN-01: Audit display reports credentials path as the symlink path, not the host path + +**File:** `claudebox.sh:268` + +**Issue:** The mounts section of `print_audit` displays: + +``` +credentials $HOME/.claude/.credentials.json (read-write) +``` + +`$HOME/.claude` on the host is the real Claude config directory (not a symlink), so the path shown is technically correct. However, once inside the sandbox `$HOME/.claude` becomes a symlink to `.claudebox`, making the display confusing when users compare the audit output with what they see on their host filesystem. Displaying the actual host source path (`$CREDS_FILE`) would be more accurate: + +```bash +printf ' %-12s %s (read-only)\n' "credentials" "$CREDS_FILE" >&2 +``` + +This also reflects the read-only fix from WR-01. + +--- + +_Reviewed: 2026-04-10_ +_Reviewer: Claude (gsd-code-reviewer)_ +_Depth: standard_ diff --git a/.planning/phases/04-auth-passthrough/04-VERIFICATION.md b/.planning/phases/04-auth-passthrough/04-VERIFICATION.md new file mode 100644 index 0000000..d6589a6 --- /dev/null +++ b/.planning/phases/04-auth-passthrough/04-VERIFICATION.md @@ -0,0 +1,98 @@ +--- +phase: 04-auth-passthrough +verified: 2026-04-10T00:00:00Z +status: passed +score: 7/7 must-haves verified +overrides_applied: 0 +re_verification: + previous_status: gaps_found + previous_score: 6/7 + gaps_closed: + - "OAuth token refresh can write back to the credentials file (read-write mount) — reverted from --ro-bind to --bind in both BWRAP_ARGS and dry-run block; print_audit mounts display updated to show (read-write)" + - "AUTH-01 and AUTH-02 requirements are tracked in REQUIREMENTS.md — both IDs added under v2 Authentication Passthrough section with definitions and traceability entries" + gaps_remaining: [] + regressions: [] +--- + +# Phase 04: auth-passthrough Verification Report + +**Phase Goal:** Mount ~/.claude/.credentials.json read-write into the sandbox and rewrite the pre-launch audit to a unified env/mounts/network display. +**Verified:** 2026-04-10 +**Status:** passed +**Re-verification:** Yes — after gap closure + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | claudebox launches successfully when ~/.claudebox/.credentials.json exists on the host | VERIFIED | Lines 107-112: CREDS_FILE set to $HOME/.claudebox/.credentials.json; CREDS_MOUNT conditional detection; BWRAP_ARGS conditional append at lines 370-371 | +| 2 | OAuth token refresh can write back to the credentials file (read-write mount) | VERIFIED | Line 371: --bind used (not --ro-bind); line 338 dry-run block also outputs --bind; print_audit line 269 shows (read-write) label | +| 3 | claudebox launches without error when ~/.claudebox/.credentials.json does not exist | VERIFIED | CREDS_MOUNT=false path: --bind simply omitted from BWRAP_ARGS; no error or warning output | +| 4 | ANTHROPIC_API_KEY is passed into the sandbox when set on the host | VERIFIED | Line 214: HOST_ALLOWLIST includes ANTHROPIC_API_KEY; conditional --setenv applied in the loop at lines 215-221 | +| 5 | The audit screen shows all env vars in a single unified list with [~]/[>]/[+] prefixes | VERIFIED | print_audit lines 242-259: three loops — sandbox [~] (green), host [>] (yellow), extra [+] (cyan) — with literal text prefixes | +| 6 | The audit screen shows a Mounts section and a Network section after the env list | VERIFIED | Lines 265-276: Mounts section (CWD, ~/.claude, conditional credentials with read-write label); Network section ("full (host network)") | +| 7 | The --dry-run output mirrors the credential bind when the file exists | VERIFIED | Lines 337-338: conditional block prints --bind $CREDS_FILE $HOME/.claudebox/.credentials.json when CREDS_MOUNT=true | + +**Score:** 7/7 truths verified + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `claudebox.sh` | Credential mount logic, updated print_audit, updated --dry-run block | VERIFIED | File exists (383 lines), substantive, all three pieces present and wired into execution path; bash -n passes | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|-----|--------|---------| +| CREDS_MOUNT detection (lines 108-112) | BWRAP_ARGS conditional append (lines 370-371) | if [[ "$CREDS_MOUNT" == true ]] | WIRED | --bind used; read-write mount | +| CREDS_MOUNT detection | dry-run display block (lines 337-338) | if [[ "$CREDS_MOUNT" == true ]] | WIRED | --bind mirrored correctly | +| print_audit function | AUDIT_SANDBOX_KEYS / AUDIT_HOST_KEYS / AUDIT_EXTRA_KEYS arrays | [~]/[>]/[+] prefix loops (lines 242-259) | WIRED | Three loops reading from correct audit arrays | +| CREDS_MOUNT | print_audit Mounts section (lines 268-270) | if [[ "$CREDS_MOUNT" == true ]] | WIRED | Conditional credentials line shows (read-write) label | + +### Data-Flow Trace (Level 4) + +Not applicable — claudebox.sh is a shell launcher script. All data flows are shell variable assignments and bwrap argument construction, not rendered dynamic UI components. + +### Behavioral Spot-Checks + +| Behavior | Command | Result | Status | +|----------|---------|--------|--------| +| Script syntax valid | bash -n claudebox.sh | SYNTAX OK | PASS | +| Credential bind is read-write (not read-only) | grep --bind.*credentials claudebox.sh | Lines 338, 371 confirm --bind | PASS | +| No --ro-bind on credentials | grep ro-bind.*credentials claudebox.sh | No output | PASS | +| [~]/[>]/[+] prefixes present | grep pattern in print_audit | Lines 244, 250, 255, 259 | PASS | +| Mounts and Network sections present | lines 265, 275 | Both sections confirmed | PASS | +| print_audit credentials label says read-write | grep read-write claudebox.sh | Lines 266, 267, 269 | PASS | + +### Requirements Coverage + +| Requirement | Source | Description | Status | Evidence | +|-------------|--------|-------------|--------|---------| +| AUTH-01 | REQUIREMENTS.md v2, Phase 4 | ~/.claudebox/.credentials.json bind-mounted read-write when file exists | SATISFIED | Defined in REQUIREMENTS.md lines 61-62; implemented at claudebox.sh lines 107-112, 370-371; traceability entry at line 128 | +| AUTH-02 | REQUIREMENTS.md v2, Phase 4 | Silent skip when credentials file absent | SATISFIED | Defined in REQUIREMENTS.md lines 63-64; implemented at claudebox.sh line 111 (CREDS_MOUNT=false); traceability entry at line 129 | + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| claudebox.sh | 276 | "full (host network)" placeholder | Info | Intentional Phase 6 placeholder; documented in SUMMARY known stubs | + +### Human Verification Required + +None. All must-haves are verified programmatically. + +### Gaps Summary + +No gaps. Both gaps from the initial verification are closed: + +**Gap 1 (closed):** Credential mount is now `--bind` (read-write) in both the actual BWRAP_ARGS (line 371) and the dry-run display block (line 338). The print_audit mounts section labels credentials as `(read-write)`. The WR-01 code-review change that had introduced `--ro-bind` was reverted per the plan's original intent (OAuth refresh requires write access). + +**Gap 2 (closed):** AUTH-01 and AUTH-02 are now defined in REQUIREMENTS.md under the v2 "Authentication Passthrough" section with full descriptions and traceability table entries showing Phase 4 / Complete. + +--- + +_Verified: 2026-04-10_ +_Verifier: Claude (gsd-verifier)_ diff --git a/claudebox.sh b/claudebox.sh index 2e485c3..a0b4d95 100644 --- a/claudebox.sh +++ b/claudebox.sh @@ -101,6 +101,16 @@ CWD=$(pwd) # Ensure ~/.claudebox exists mkdir -p "$HOME/.claudebox" +# Credential file mount (AUTH-01, AUTH-02) +# Use ~/.claudebox (the host-side claudebox config dir), not ~/.claude +# ~/.claude -> ~/.claudebox symlink only exists inside the sandbox at runtime +CREDS_FILE="$HOME/.claudebox/.credentials.json" +if [[ -f "$CREDS_FILE" ]]; then + CREDS_MOUNT=true +else + CREDS_MOUNT=false +fi + # === Sandbox-aware prompting (AWARE-01, AWARE-02) === # Write SANDBOX.md -- fully managed, overwritten every launch (D-02) @@ -228,38 +238,42 @@ print_audit() { echo "${BOLD}${CYAN}=== Sandbox Environment ===${RESET}" >&2 echo "" >&2 - # Sandbox-generated (D-01) - echo "${BOLD}Sandbox-generated:${RESET}" >&2 + # Unified env list: sandbox [~], host allowlisted [>], extra [+] (D-06, D-07, D-08, D-09, D-10) for var in "${AUDIT_SANDBOX_KEYS[@]}"; do if [[ "$var" == "PATH" ]]; then - echo " ${GREEN}PATH=${RESET}" >&2 + echo " ${GREEN}[~]${RESET} PATH=" >&2 IFS=':' read -ra path_entries <<< "${AUDIT_SANDBOX_VALS[PATH]}" for entry in "${path_entries[@]}"; do - echo " ${DIM}${entry}${RESET}" >&2 + echo " ${DIM}${entry}${RESET}" >&2 done else - echo " ${GREEN}${var}=${RESET}$(mask_value "$var" "${AUDIT_SANDBOX_VALS[$var]}")" >&2 + echo " ${GREEN}[~]${RESET} ${var}=$(mask_value "$var" "${AUDIT_SANDBOX_VALS[$var]}")" >&2 fi done + + for var in "${AUDIT_HOST_KEYS[@]}"; do + echo " ${YELLOW}[>]${RESET} ${var}=$(mask_value "$var" "${AUDIT_HOST_VALS[$var]}")" >&2 + done + + for var in "${AUDIT_EXTRA_KEYS[@]}"; do + echo " ${CYAN}[+]${RESET} ${var}=$(mask_value "$var" "${AUDIT_EXTRA_VALS[$var]}")" >&2 + done + echo "" >&2 - # Host allowlisted (D-01) - if (( ${#AUDIT_HOST_KEYS[@]} > 0 )); then - echo "${BOLD}Host (allowlisted):${RESET}" >&2 - for var in "${AUDIT_HOST_KEYS[@]}"; do - echo " ${YELLOW}${var}=${RESET}$(mask_value "$var" "${AUDIT_HOST_VALS[$var]}")" >&2 - done - echo "" >&2 + # Mounts section + echo "${BOLD}Mounts:${RESET}" >&2 + printf ' %-12s %s (read-write)\n' "CWD" "$CWD" >&2 + printf ' %-12s %s (read-write)\n' "$HOME/.claude" "$HOME/.claudebox" >&2 + if [[ "$CREDS_MOUNT" == true ]]; then + printf ' %-12s %s (read-write)\n' "credentials" "$CREDS_FILE" >&2 fi - # Extra from CLAUDEBOX_EXTRA_ENV (D-01) - if (( ${#AUDIT_EXTRA_KEYS[@]} > 0 )); then - echo "${BOLD}Extra (CLAUDEBOX_EXTRA_ENV):${RESET}" >&2 - for var in "${AUDIT_EXTRA_KEYS[@]}"; do - echo " ${YELLOW}${var}=${RESET}$(mask_value "$var" "${AUDIT_EXTRA_VALS[$var]}")" >&2 - done - echo "" >&2 - fi + echo "" >&2 + + # Network section (Phase 4 placeholder — full isolation comes in Phase 6) + echo "${BOLD}Network:${RESET}" >&2 + echo " full (host network)" >&2 } # Env audit and confirmation (D-05, D-06, D-07, UX-01, UX-02, UX-03) @@ -293,10 +307,15 @@ if [[ "$DRY_RUN" == true ]]; then { echo "bwrap \\" echo " --clearenv \\" + # Guard: ENV_ARGS must be a multiple of 3 (--setenv NAME VALUE triplets) + if (( ${#ENV_ARGS[@]} % 3 != 0 )); then + echo "BUG: ENV_ARGS length ${#ENV_ARGS[@]} is not a multiple of 3" >&2 + exit 1 + fi dry_run_i=0 while (( dry_run_i < ${#ENV_ARGS[@]} )); do printf ' %s %s %q \\\n' "${ENV_ARGS[$dry_run_i]}" "${ENV_ARGS[$((dry_run_i+1))]}" "${ENV_ARGS[$((dry_run_i+2))]}" - (( dry_run_i += 3 )) + dry_run_i=$(( dry_run_i + 3 )) done echo " --tmpfs / \\" echo " --proc /proc \\" @@ -315,6 +334,9 @@ if [[ "$DRY_RUN" == true ]]; then echo " --tmpfs $HOME \\" echo " --bind $HOME/.claudebox $HOME/.claudebox \\" echo " --symlink $HOME/.claudebox $HOME/.claude \\" + if [[ "$CREDS_MOUNT" == true ]]; then + echo " --bind $CREDS_FILE $HOME/.claudebox/.credentials.json \\" + fi printf ' --ro-bind %q %s/.gitconfig \\\n' "$GITCONFIG_TMP" "$HOME" echo " --bind $CWD $CWD \\" echo " --chdir $CWD \\" @@ -323,28 +345,38 @@ if [[ "$DRY_RUN" == true ]]; then exit 0 fi +# Build bwrap mount args array (allows conditional mounts) +BWRAP_ARGS=( + --clearenv + "${ENV_ARGS[@]}" + --tmpfs / + --proc /proc + --dev /dev + --tmpfs /tmp + --ro-bind /nix/store /nix/store + --bind /nix/var/nix /nix/var/nix + --ro-bind /etc/resolv.conf /etc/resolv.conf + --ro-bind /etc/ssl /etc/ssl + --ro-bind /etc/passwd /etc/passwd + --ro-bind /etc/group /etc/group + --ro-bind /etc/hosts /etc/hosts + --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf + --ro-bind /etc/nix /etc/nix + --symlink "$(readlink -f "$(command -v env)")" /usr/bin/env + --tmpfs "$HOME" + --bind "$HOME/.claudebox" "$HOME/.claudebox" + --symlink "$HOME/.claudebox" "$HOME/.claude" +) +if [[ "$CREDS_MOUNT" == true ]]; then + BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json") +fi +BWRAP_ARGS+=( + --ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig" + --bind "$CWD" "$CWD" + --chdir "$CWD" + -- + "${SANDBOX_CMD[@]}" +) + # exec bwrap (SAND-04 through SAND-15, UX-06, D-01) -exec bwrap \ - --clearenv \ - "${ENV_ARGS[@]}" \ - --tmpfs / \ - --proc /proc \ - --dev /dev \ - --tmpfs /tmp \ - --ro-bind /nix/store /nix/store \ - --bind /nix/var/nix /nix/var/nix \ - --ro-bind /etc/resolv.conf /etc/resolv.conf \ - --ro-bind /etc/ssl /etc/ssl \ - --ro-bind /etc/passwd /etc/passwd \ - --ro-bind /etc/group /etc/group \ - --ro-bind /etc/hosts /etc/hosts \ - --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \ - --ro-bind /etc/nix /etc/nix \ - --symlink "$(readlink -f "$(command -v env)")" /usr/bin/env \ - --tmpfs "$HOME" \ - --bind "$HOME/.claudebox" "$HOME/.claudebox" \ - --symlink "$HOME/.claudebox" "$HOME/.claude" \ - --ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig" \ - --bind "$CWD" "$CWD" \ - --chdir "$CWD" \ - -- "${SANDBOX_CMD[@]}" +exec bwrap "${BWRAP_ARGS[@]}"