Gsd/Phase 04 Auth Passthrough #1

Merged
toph merged 18 commits from gsd/phase-04-auth-passthrough into main 2026-04-10 12:27:33 +00:00
7 changed files with 543 additions and 55 deletions

View file

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

View file

@ -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%

View 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

View 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_

View 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_

View 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)_

View file

@ -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
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
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
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
# 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
# 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[@]}"