Gsd/Phase 04 Auth Passthrough #1
7 changed files with 543 additions and 55 deletions
|
|
@ -56,6 +56,11 @@
|
||||||
|
|
||||||
## v2 Requirements
|
## 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
|
### Network Isolation
|
||||||
|
|
||||||
- **NET-01**: Block LAN/Tailscale access (RFC1918 + 100.64.0.0/10) while allowing internet egress
|
- **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-01 | Phase 1 | Complete |
|
||||||
| NIX-02 | Phase 1 | Complete |
|
| NIX-02 | Phase 1 | Complete |
|
||||||
| NIX-03 | Phase 1 | Complete |
|
| NIX-03 | Phase 1 | Complete |
|
||||||
|
| AUTH-01 | Phase 4 | Complete |
|
||||||
|
| AUTH-02 | Phase 4 | Complete |
|
||||||
|
|
||||||
**Coverage:**
|
**Coverage:**
|
||||||
- v1 requirements: 31 total
|
- v1 requirements: 31 total, v2 requirements (partial): 2
|
||||||
- Mapped to phases: 31
|
- Mapped to phases: 33
|
||||||
- Unmapped: 0
|
- Unmapped: 0
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Phase 3 context gathered
|
stopped_at: Phase 3 context gathered
|
||||||
last_updated: "2026-04-09T19:24:16.913Z"
|
last_updated: "2026-04-10T09:33:52.025Z"
|
||||||
last_activity: 2026-04-09
|
last_activity: 2026-04-10
|
||||||
progress:
|
progress:
|
||||||
total_phases: 3
|
total_phases: 3
|
||||||
completed_phases: 3
|
completed_phases: 0
|
||||||
total_plans: 5
|
total_plans: 0
|
||||||
completed_plans: 5
|
completed_plans: 0
|
||||||
percent: 100
|
percent: 33
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
|
|
@ -25,10 +25,10 @@ See: .planning/PROJECT.md (updated 2026-04-09)
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 03 of 3 (sandbox aware prompting)
|
Phase: 04 of 3 (sandbox aware prompting)
|
||||||
Plan: Not started
|
Plan: Not started
|
||||||
Status: Ready to execute
|
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%
|
Progress: [███░░░░░░░] 33%
|
||||||
|
|
||||||
|
|
|
||||||
120
.planning/phases/04-auth-passthrough/04-01-SUMMARY.md
Normal file
120
.planning/phases/04-auth-passthrough/04-01-SUMMARY.md
Normal file
|
|
@ -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
|
||||||
59
.planning/phases/04-auth-passthrough/04-REVIEW-FIX.md
Normal file
59
.planning/phases/04-auth-passthrough/04-REVIEW-FIX.md
Normal file
|
|
@ -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_
|
||||||
172
.planning/phases/04-auth-passthrough/04-REVIEW.md
Normal file
172
.planning/phases/04-auth-passthrough/04-REVIEW.md
Normal file
|
|
@ -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_
|
||||||
98
.planning/phases/04-auth-passthrough/04-VERIFICATION.md
Normal file
98
.planning/phases/04-auth-passthrough/04-VERIFICATION.md
Normal file
|
|
@ -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)_
|
||||||
114
claudebox.sh
114
claudebox.sh
|
|
@ -101,6 +101,16 @@ CWD=$(pwd)
|
||||||
# Ensure ~/.claudebox exists
|
# Ensure ~/.claudebox exists
|
||||||
mkdir -p "$HOME/.claudebox"
|
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) ===
|
# === Sandbox-aware prompting (AWARE-01, AWARE-02) ===
|
||||||
|
|
||||||
# Write SANDBOX.md -- fully managed, overwritten every launch (D-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 "${BOLD}${CYAN}=== Sandbox Environment ===${RESET}" >&2
|
||||||
echo "" >&2
|
echo "" >&2
|
||||||
|
|
||||||
# Sandbox-generated (D-01)
|
# Unified env list: sandbox [~], host allowlisted [>], extra [+] (D-06, D-07, D-08, D-09, D-10)
|
||||||
echo "${BOLD}Sandbox-generated:${RESET}" >&2
|
|
||||||
for var in "${AUDIT_SANDBOX_KEYS[@]}"; do
|
for var in "${AUDIT_SANDBOX_KEYS[@]}"; do
|
||||||
if [[ "$var" == "PATH" ]]; then
|
if [[ "$var" == "PATH" ]]; then
|
||||||
echo " ${GREEN}PATH=${RESET}" >&2
|
echo " ${GREEN}[~]${RESET} PATH=" >&2
|
||||||
IFS=':' read -ra path_entries <<< "${AUDIT_SANDBOX_VALS[PATH]}"
|
IFS=':' read -ra path_entries <<< "${AUDIT_SANDBOX_VALS[PATH]}"
|
||||||
for entry in "${path_entries[@]}"; do
|
for entry in "${path_entries[@]}"; do
|
||||||
echo " ${DIM}${entry}${RESET}" >&2
|
echo " ${DIM}${entry}${RESET}" >&2
|
||||||
done
|
done
|
||||||
else
|
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
|
fi
|
||||||
done
|
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
|
for var in "${AUDIT_HOST_KEYS[@]}"; do
|
||||||
echo " ${YELLOW}${var}=${RESET}$(mask_value "$var" "${AUDIT_HOST_VALS[$var]}")" >&2
|
echo " ${YELLOW}[>]${RESET} ${var}=$(mask_value "$var" "${AUDIT_HOST_VALS[$var]}")" >&2
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for var in "${AUDIT_EXTRA_KEYS[@]}"; do
|
||||||
|
echo " ${CYAN}[+]${RESET} ${var}=$(mask_value "$var" "${AUDIT_EXTRA_VALS[$var]}")" >&2
|
||||||
|
done
|
||||||
|
|
||||||
echo "" >&2
|
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
|
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
|
echo "" >&2
|
||||||
fi
|
|
||||||
|
# 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)
|
# 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 "bwrap \\"
|
||||||
echo " --clearenv \\"
|
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
|
dry_run_i=0
|
||||||
while (( dry_run_i < ${#ENV_ARGS[@]} )); do
|
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))]}"
|
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
|
done
|
||||||
echo " --tmpfs / \\"
|
echo " --tmpfs / \\"
|
||||||
echo " --proc /proc \\"
|
echo " --proc /proc \\"
|
||||||
|
|
@ -315,6 +334,9 @@ if [[ "$DRY_RUN" == true ]]; then
|
||||||
echo " --tmpfs $HOME \\"
|
echo " --tmpfs $HOME \\"
|
||||||
echo " --bind $HOME/.claudebox $HOME/.claudebox \\"
|
echo " --bind $HOME/.claudebox $HOME/.claudebox \\"
|
||||||
echo " --symlink $HOME/.claudebox $HOME/.claude \\"
|
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"
|
printf ' --ro-bind %q %s/.gitconfig \\\n' "$GITCONFIG_TMP" "$HOME"
|
||||||
echo " --bind $CWD $CWD \\"
|
echo " --bind $CWD $CWD \\"
|
||||||
echo " --chdir $CWD \\"
|
echo " --chdir $CWD \\"
|
||||||
|
|
@ -323,28 +345,38 @@ if [[ "$DRY_RUN" == true ]]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
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 (SAND-04 through SAND-15, UX-06, D-01)
|
||||||
exec bwrap \
|
exec bwrap "${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" \
|
|
||||||
--ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig" \
|
|
||||||
--bind "$CWD" "$CWD" \
|
|
||||||
--chdir "$CWD" \
|
|
||||||
-- "${SANDBOX_CMD[@]}"
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue