From 0a4dba4c3c2cfd783857087e58c13ec5504c1fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=BChl?= Date: Thu, 9 Apr 2026 16:34:14 +0200 Subject: [PATCH] docs(02): create phase plan --- .planning/ROADMAP.md | 9 +- .../02-env-audit-and-cli-polish/02-01-PLAN.md | 296 ++++++++++++++++ .../02-env-audit-and-cli-polish/02-02-PLAN.md | 325 ++++++++++++++++++ 3 files changed, 626 insertions(+), 4 deletions(-) create mode 100644 .planning/phases/02-env-audit-and-cli-polish/02-01-PLAN.md create mode 100644 .planning/phases/02-env-audit-and-cli-polish/02-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 20f34c9..e3fcc9e 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -43,10 +43,11 @@ Plans: 2. Running `claudebox --yes` or `claudebox -y` skips the env audit and launches immediately 3. Running `claudebox --dry-run` prints the full bwrap command without executing it 4. Running `claudebox --check` reports whether bwrap exists, required Nix packages are available, and ~/.claudebox exists -**Plans**: TBD +**Plans:** 2 plans Plans: -- [ ] 02-01: TBD +- [ ] 02-01-PLAN.md -- Refactor flag parsing, add --check and --dry-run modes +- [ ] 02-02-PLAN.md -- Env audit display with grouping, masking, and confirmation prompt ### Phase 3: Sandbox-Aware Prompting **Goal**: Claude inside the sandbox knows it is sandboxed, how to install tools, and what is unavailable @@ -67,6 +68,6 @@ Phases execute in numeric order: 1 -> 2 -> 3 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Minimal Viable Sandbox | 0/2 | Planned | - | -| 2. Env Audit and CLI Polish | 0/1 | Not started | - | +| 1. Minimal Viable Sandbox | 2/2 | Complete | - | +| 2. Env Audit and CLI Polish | 0/2 | Planned | - | | 3. Sandbox-Aware Prompting | 0/1 | Not started | - | diff --git a/.planning/phases/02-env-audit-and-cli-polish/02-01-PLAN.md b/.planning/phases/02-env-audit-and-cli-polish/02-01-PLAN.md new file mode 100644 index 0000000..581338f --- /dev/null +++ b/.planning/phases/02-env-audit-and-cli-polish/02-01-PLAN.md @@ -0,0 +1,296 @@ +--- +phase: 02-env-audit-and-cli-polish +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - claudebox.sh +autonomous: true +requirements: + - UX-03 + - UX-04 + - UX-05 +must_haves: + truths: + - "claudebox --check reports pass/fail for bwrap, claude, git, nix, ~/.claudebox, and ANTHROPIC_API_KEY" + - "claudebox --dry-run prints the full bwrap command with all flags and exits without executing" + - "claudebox --yes -y flags are recognized and stored for Plan 02 audit skip" + - "Unknown flags are passed through to claude in CLAUDE_ARGS array" + - "Flag parsing handles multiple claudebox flags in any order" + artifacts: + - path: "claudebox.sh" + provides: "Refactored flag parsing, --check, --dry-run" + contains: "CHECK_MODE|DRY_RUN|SKIP_AUDIT|CLAUDE_ARGS" + key_links: + - from: "claudebox.sh flag parsing" + to: "SANDBOX_CMD construction" + via: "CLAUDE_ARGS array replaces raw $@" + pattern: "CLAUDE_ARGS" +--- + + +Refactor claudebox.sh flag parsing to support multiple flags, then add --check and --dry-run early-exit modes. + +Purpose: Foundation for all Phase 2 CLI flags. The current single-flag parser (for/shift/break) cannot handle multiple claudebox flags. This plan refactors it to a while/shift loop that collects claudebox flags and accumulates remaining args in CLAUDE_ARGS. Then adds two diagnostic modes that exit before sandbox launch. + +Output: claudebox.sh with working --check, --dry-run, and flag scaffolding for --yes/-y. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/02-env-audit-and-cli-polish/02-CONTEXT.md +@.planning/phases/02-env-audit-and-cli-polish/02-RESEARCH.md + +@claudebox.sh +@flake.nix + + + + + + + + + + + + + Task 1: Refactor flag parsing to while/shift with CLAUDE_ARGS accumulator + claudebox.sh + claudebox.sh + +Replace the current flag parsing block (lines 1-9) with a while/shift loop that handles multiple claudebox flags. Define these variables at the top of the script: + +```bash +SKIP_AUDIT=false +DRY_RUN=false +CHECK_MODE=false +SHELL_MODE=false +CLAUDE_ARGS=() + +while (( $# > 0 )); do + case "$1" in + --yes|-y) SKIP_AUDIT=true ;; + --dry-run) DRY_RUN=true ;; + --check) CHECK_MODE=true ;; + --shell) SHELL_MODE=true ;; + --) shift; CLAUDE_ARGS+=("$@"); break ;; + *) CLAUDE_ARGS+=("$1") ;; + esac + shift +done +``` + +Per D-08, --yes/-y sets SKIP_AUDIT=true (consumed by Plan 02's audit display). + +Then update the SANDBOX_CMD construction (currently lines 70-74) to use CLAUDE_ARGS instead of $@: + +```bash +if [[ "$SHELL_MODE" == true ]]; then + SANDBOX_CMD=("$SANDBOX_BASH" "${CLAUDE_ARGS[@]}") +else + SANDBOX_CMD=("$CLAUDE_BIN" --dangerously-skip-permissions "${CLAUDE_ARGS[@]}") +fi +``` + +This ensures unknown flags like `--model sonnet` pass through to claude correctly. + + + cd /home/toph/code/tools/claudebox && grep -q 'SKIP_AUDIT=false' claudebox.sh && grep -q 'DRY_RUN=false' claudebox.sh && grep -q 'CHECK_MODE=false' claudebox.sh && grep -q 'CLAUDE_ARGS' claudebox.sh && grep -q 'while (( \$# > 0 ))' claudebox.sh && echo "PASS: flag parsing refactored" + + + - claudebox.sh contains `SKIP_AUDIT=false` variable declaration + - claudebox.sh contains `DRY_RUN=false` variable declaration + - claudebox.sh contains `CHECK_MODE=false` variable declaration + - claudebox.sh contains `SHELL_MODE=false` variable declaration + - claudebox.sh contains `CLAUDE_ARGS=()` array declaration + - claudebox.sh contains `while (( $# > 0 ))` loop (not `for arg in`) + - claudebox.sh contains `CLAUDE_ARGS+=("$1")` for unknown flag accumulation + - SANDBOX_CMD uses `"${CLAUDE_ARGS[@]}"` not `"$@"` + - The old `for arg in "$@"` parsing block is removed + + Flag parsing handles --yes, -y, --dry-run, --check, --shell, and passes unknown flags to CLAUDE_ARGS. SANDBOX_CMD uses CLAUDE_ARGS. + + + + Task 2: Add --dry-run mode that prints full bwrap command and exits + claudebox.sh + claudebox.sh + +Per D-09 (UX-04), add a --dry-run handler after the SANDBOX_CMD construction and before the `exec bwrap` line. When DRY_RUN=true, print the complete bwrap invocation in a readable multiline format to stderr, then exit 0. Do NOT prompt for confirmation (--dry-run implies --yes per research open question 1). + +Insert this block right before `exec bwrap`: + +```bash +# --dry-run: print the bwrap command without executing (D-09, UX-04) +if [[ "$DRY_RUN" == true ]]; then + { + echo "bwrap \\" + echo " --clearenv \\" + # Print each ENV_ARGS entry + local_i=0 + while (( local_i < ${#ENV_ARGS[@]} )); do + printf ' %s %s %q \\\n' "${ENV_ARGS[$local_i]}" "${ENV_ARGS[$((local_i+1))]}" "${ENV_ARGS[$((local_i+2))]}" + (( local_i += 3 )) + done + echo " --tmpfs / \\" + echo " --proc /proc \\" + echo " --dev /dev \\" + echo " --tmpfs /tmp \\" + echo " --ro-bind /nix/store /nix/store \\" + echo " --bind /nix/var/nix /nix/var/nix \\" + echo " --ro-bind /etc/resolv.conf /etc/resolv.conf \\" + echo " --ro-bind /etc/ssl /etc/ssl \\" + echo " --ro-bind /etc/static /etc/static \\" + echo " --ro-bind /etc/passwd /etc/passwd \\" + echo " --ro-bind /etc/group /etc/group \\" + echo " --ro-bind /etc/hosts /etc/hosts \\" + echo " --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \\" + echo " --ro-bind /etc/nix /etc/nix \\" + printf ' --symlink %q /usr/bin/env \\\n' "$(readlink -f "$(command -v env)")" + echo " --tmpfs $HOME \\" + echo " --bind $HOME/.claudebox $HOME/.claude \\" + printf ' --ro-bind %q %s/.gitconfig \\\n' "$GITCONFIG_TMP" "$HOME" + echo " --bind $CWD $CWD \\" + echo " --chdir $CWD \\" + printf ' -- %s\n' "${SANDBOX_CMD[*]}" + } >&2 + exit 0 +fi +``` + +Note: The ENV_ARGS array is structured as triplets (--setenv key value), so iterate in steps of 3. Use `printf %q` for values that might contain special chars. The filesystem mount flags mirror the actual `exec bwrap` call exactly -- if the bwrap call changes, this must be updated too. + +Important: Since writeShellApplication uses `set -euo pipefail` and shellcheck, avoid using `local` outside functions. Use a unique variable name like `dry_run_i` instead of `local_i` for the loop counter. + + + cd /home/toph/code/tools/claudebox && grep -q 'DRY_RUN.*true' claudebox.sh && grep -q 'dry.run' claudebox.sh && grep -c 'exit 0' claudebox.sh | grep -q '[1-9]' && echo "PASS: dry-run handler present" + + + - claudebox.sh contains an `if [[ "$DRY_RUN" == true ]]` block + - The dry-run block prints `bwrap \` as first line to stderr + - The dry-run block prints `--clearenv \` on its own line + - The dry-run block iterates ENV_ARGS in steps of 3 to print --setenv triplets + - The dry-run block prints all filesystem mount flags matching the actual exec bwrap call + - The dry-run block ends with `exit 0` + - All dry-run output goes to stderr (>&2) + - `nix build` succeeds (shellcheck passes) + + Running `claudebox --dry-run` prints the full bwrap command in multiline format to stderr and exits 0 without launching the sandbox. + + + + Task 3: Add --check mode that verifies prerequisites and exits + claudebox.sh + claudebox.sh + +Per D-10 (UX-05), add a --check handler as an early exit right after flag parsing and before any binary resolution or env construction. When CHECK_MODE=true, run diagnostic checks and exit. + +Insert this block immediately after the flag parsing while/shift loop: + +```bash +# --check: verify prerequisites and exit (D-10, UX-05) +if [[ "$CHECK_MODE" == true ]]; then + pass=true + green=$'\033[32m' red=$'\033[31m' yellow=$'\033[33m' reset=$'\033[0m' + + check_cmd() { + if command -v "$1" &>/dev/null; then + echo "${green}OK${reset} $1" >&2 + else + echo "${red}FAIL${reset} $1 -- not found" >&2 + pass=false + fi + } + + echo "claudebox prerequisites:" >&2 + echo "" >&2 + check_cmd bwrap + check_cmd claude + check_cmd git + check_cmd curl + check_cmd nix + + if [[ -d "$HOME/.claudebox" ]]; then + echo "${green}OK${reset} ~/.claudebox exists" >&2 + else + echo "${red}FAIL${reset} ~/.claudebox -- not found (will be created on first run)" >&2 + fi + + if [[ -v ANTHROPIC_API_KEY ]]; then + echo "${green}OK${reset} ANTHROPIC_API_KEY is set" >&2 + else + echo "${yellow}WARN${reset} ANTHROPIC_API_KEY is not set" >&2 + fi + + echo "" >&2 + if [[ "$pass" == true ]]; then + echo "${green}All checks passed.${reset}" >&2 + exit 0 + else + echo "${red}Some checks failed.${reset}" >&2 + exit 1 + fi +fi +``` + +The check verifies: bwrap, claude, git, curl, nix (required binaries), ~/.claudebox directory, and ANTHROPIC_API_KEY (warn only, not a hard failure). Exit 0 if all required checks pass, exit 1 if any fail. All output to stderr per D-07. + + + cd /home/toph/code/tools/claudebox && grep -q 'CHECK_MODE.*true' claudebox.sh && grep -q 'check_cmd' claudebox.sh && grep -q 'prerequisites' claudebox.sh && echo "PASS: check handler present" + + + - claudebox.sh contains an `if [[ "$CHECK_MODE" == true ]]` block + - The check block is positioned BEFORE binary resolution (`command -v bash`, `command -v claude`) + - The check block tests for: bwrap, claude, git, curl, nix via `command -v` + - The check block tests for `$HOME/.claudebox` directory existence + - The check block tests for ANTHROPIC_API_KEY with WARN (not FAIL) + - The check block prints colored OK/FAIL/WARN indicators to stderr + - The check block exits 0 on all-pass, exits 1 on any FAIL + - `nix build` succeeds (shellcheck passes) + + Running `claudebox --check` prints pass/fail for each prerequisite to stderr and exits with appropriate code. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| host env -> --dry-run output | Env var values printed to stderr may contain secrets | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-02-01 | Information Disclosure | --dry-run printing env values | accept | --dry-run shows env values unmasked because the user explicitly asked to see the full command. Plan 02 adds masking for the audit display. The dry-run user is debugging, not reviewing for leaks. | +| T-02-02 | Information Disclosure | --check printing ANTHROPIC_API_KEY status | mitigate | Only print presence/absence, never the value. Use `[[ -v ANTHROPIC_API_KEY ]]` not echo of value. | + + + +1. `grep -c 'CLAUDE_ARGS' claudebox.sh` returns >= 3 (declaration + accumulation + usage) +2. `grep 'DRY_RUN\|CHECK_MODE\|SKIP_AUDIT' claudebox.sh` shows all three flag variables +3. `nix build` succeeds (shellcheck validation) + + + +- Flag parsing supports --yes, -y, --dry-run, --check, --shell, and -- separator +- Unknown flags accumulate in CLAUDE_ARGS and pass through to claude +- --check exits early with colored diagnostic output +- --dry-run prints full bwrap command and exits +- shellcheck passes via nix build + + + +After completion, create `.planning/phases/02-env-audit-and-cli-polish/02-01-SUMMARY.md` + diff --git a/.planning/phases/02-env-audit-and-cli-polish/02-02-PLAN.md b/.planning/phases/02-env-audit-and-cli-polish/02-02-PLAN.md new file mode 100644 index 0000000..6883028 --- /dev/null +++ b/.planning/phases/02-env-audit-and-cli-polish/02-02-PLAN.md @@ -0,0 +1,325 @@ +--- +phase: 02-env-audit-and-cli-polish +plan: 02 +type: execute +wave: 2 +depends_on: + - "02-01" +files_modified: + - claudebox.sh +autonomous: true +requirements: + - UX-01 + - UX-02 +must_haves: + truths: + - "Running claudebox without --yes shows all env vars grouped by source before launching" + - "Env vars are grouped into Sandbox-generated, Host (allowlisted), and Extra (CLAUDEBOX_EXTRA_ENV) sections" + - "PATH is displayed split by colon with one entry per line" + - "Values matching *KEY*, *TOKEN*, *SECRET*, *PASSWORD*, *CREDENTIAL* are auto-masked" + - "User sees a Proceed? [Y/n] prompt and can abort by typing n" + - "Non-interactive stdin (piped, CI) aborts with error telling user to pass --yes/-y" + - "All audit output goes to stderr, stdout stays clean" + artifacts: + - path: "claudebox.sh" + provides: "Env audit display, masking, confirmation prompt" + contains: "mask_value|print_audit|Proceed" + key_links: + - from: "claudebox.sh env audit" + to: "claudebox.sh SKIP_AUDIT flag" + via: "if [[ $SKIP_AUDIT != true ]]" + pattern: "SKIP_AUDIT" + - from: "claudebox.sh audit display" + to: "ENV_ARGS array" + via: "parallel display arrays populated during env construction" + pattern: "AUDIT_SANDBOX\\|AUDIT_HOST\\|AUDIT_EXTRA" +--- + + +Add pre-launch env audit display with grouped sections, value masking, and confirmation prompt to claudebox.sh. + +Purpose: Transparency before sandbox launch. The user sees exactly what environment enters the sandbox, with sensitive values masked, and can abort if something looks wrong. Non-interactive environments are forced to use --yes. + +Output: claudebox.sh with full env audit display and interactive confirmation, skippable via --yes/-y (flag already parsed by Plan 01). + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/phases/02-env-audit-and-cli-polish/02-CONTEXT.md +@.planning/phases/02-env-audit-and-cli-polish/02-RESEARCH.md +@.planning/phases/02-env-audit-and-cli-polish/02-01-SUMMARY.md + +@claudebox.sh +@flake.nix + + + + + + + + + + + + + + + Task 1: Add parallel display arrays and env audit display function + claudebox.sh + claudebox.sh + +Add parallel display data structures alongside the existing ENV_ARGS construction, then add a display function. This task implements D-01, D-02, D-03, D-04, D-07. + +**Step 1: Add ANSI color constants and mask_value function near the top of the script (after flag parsing, before binary resolution):** + +```bash +# ANSI formatting (D-03) +if [[ -t 2 ]] && [[ "${NO_COLOR:-}" == "" ]]; then + BOLD=$'\033[1m' + RESET=$'\033[0m' + DIM=$'\033[2m' + CYAN=$'\033[36m' + YELLOW=$'\033[33m' + GREEN=$'\033[32m' + RED=$'\033[31m' +else + BOLD="" RESET="" DIM="" CYAN="" YELLOW="" GREEN="" RED="" +fi + +# Mask sensitive values (D-04) +mask_value() { + local name="$1" value="$2" + local upper="${name^^}" + if [[ "$upper" == *KEY* || "$upper" == *TOKEN* || "$upper" == *SECRET* || "$upper" == *PASSWORD* || "$upper" == *CREDENTIAL* ]]; then + if (( ${#value} > 11 )); then + echo "${value:0:7}...${value: -4}" + else + echo "***" + fi + else + echo "$value" + fi +} +``` + +**Step 2: Add display tracking arrays.** Declare these right before the ENV_ARGS construction block: + +```bash +# Parallel display data for env audit (D-01) +declare -a AUDIT_SANDBOX_KEYS=() +declare -A AUDIT_SANDBOX_VALS=() +declare -a AUDIT_HOST_KEYS=() +declare -A AUDIT_HOST_VALS=() +declare -a AUDIT_EXTRA_KEYS=() +declare -A AUDIT_EXTRA_VALS=() +``` + +**Step 3: Populate display arrays alongside ENV_ARGS.** After each --setenv addition to ENV_ARGS, also record in the audit arrays. + +For sandbox-generated vars, after the ENV_ARGS=(...) block, add: +```bash +AUDIT_SANDBOX_KEYS=(HOME USER PATH SHELL TMPDIR XDG_RUNTIME_DIR NIX_SSL_CERT_FILE SSL_CERT_FILE) +AUDIT_SANDBOX_VALS[HOME]="$HOME" +AUDIT_SANDBOX_VALS[USER]="$USER" +AUDIT_SANDBOX_VALS[PATH]="$SANDBOX_PATH" +AUDIT_SANDBOX_VALS[SHELL]="$SANDBOX_BASH" +AUDIT_SANDBOX_VALS[TMPDIR]="/tmp" +AUDIT_SANDBOX_VALS[XDG_RUNTIME_DIR]="/tmp" +AUDIT_SANDBOX_VALS[NIX_SSL_CERT_FILE]="/etc/ssl/certs/ca-certificates.crt" +AUDIT_SANDBOX_VALS[SSL_CERT_FILE]="/etc/ssl/certs/ca-certificates.crt" +``` + +For host allowlisted vars, inside the existing HOST_ALLOWLIST loop, add after each `ENV_ARGS+=`: +```bash +AUDIT_HOST_KEYS+=("$var") +AUDIT_HOST_VALS[$var]="${!var}" +``` + +For CLAUDEBOX_EXTRA_ENV vars, inside the existing extras loop, add after each `ENV_ARGS+=`: +```bash +AUDIT_EXTRA_KEYS+=("$var") +AUDIT_EXTRA_VALS[$var]="${!var}" +``` + +**Step 4: Add the print_audit function** (after the display arrays are populated, before the dry-run check): + +```bash +# Env audit display (D-01, D-02, D-03, D-04, D-07, UX-01) +print_audit() { + echo "${BOLD}${CYAN}=== Sandbox Environment ===${RESET}" >&2 + echo "" >&2 + + # Sandbox-generated (D-01) + echo "${BOLD}Sandbox-generated:${RESET}" >&2 + for var in "${AUDIT_SANDBOX_KEYS[@]}"; do + if [[ "$var" == "PATH" ]]; then + echo " ${GREEN}PATH=${RESET}" >&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 + 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 + done + echo "" >&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 +} +``` + +All output goes to stderr per D-07. + + + cd /home/toph/code/tools/claudebox && grep -q 'mask_value' claudebox.sh && grep -q 'print_audit' claudebox.sh && grep -q 'AUDIT_SANDBOX_KEYS' claudebox.sh && grep -q 'AUDIT_HOST_KEYS' claudebox.sh && grep -q 'AUDIT_EXTRA_KEYS' claudebox.sh && grep -q 'NO_COLOR' claudebox.sh && echo "PASS: audit display infrastructure present" + + + - claudebox.sh contains `mask_value()` function + - mask_value checks for KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL (case-insensitive via `${name^^}`) + - mask_value shows first 7 + last 4 chars with `...` for values longer than 11 + - mask_value shows `***` for values 11 chars or shorter + - claudebox.sh contains `print_audit()` function + - print_audit displays three sections: "Sandbox-generated:", "Host (allowlisted):", "Extra (CLAUDEBOX_EXTRA_ENV):" + - PATH display splits by colon with one entry per line (indented) + - All audit output uses `>&2` for stderr + - ANSI colors are suppressed when stderr is not a TTY or NO_COLOR is set + - AUDIT_SANDBOX_KEYS, AUDIT_HOST_KEYS, AUDIT_EXTRA_KEYS arrays exist and are populated + - `nix build` succeeds (shellcheck passes) + + Env audit display function exists with grouped sections, PATH splitting, value masking, and ANSI formatting. Display data is tracked in parallel arrays alongside ENV_ARGS. + + + + Task 2: Add confirmation prompt with TTY detection and wire audit into launch flow + claudebox.sh + claudebox.sh + +Wire the audit display and confirmation prompt into the launch flow. This implements D-05, D-06, UX-02. + +Insert the following block AFTER print_audit is defined and BEFORE the --dry-run check (or before `exec bwrap` if dry-run check is already there). The audit+prompt should run before dry-run because dry-run implies --yes. + +Actually, looking at the flow: --dry-run should skip the audit (per research recommendation). So the order should be: + +1. If DRY_RUN is true, skip audit (it implies --yes) +2. If SKIP_AUDIT is NOT true and NOT DRY_RUN, show audit and prompt + +Add this block after the env construction and print_audit function definition, before the dry-run check: + +```bash +# Env audit and confirmation (D-05, D-06, D-07, UX-01, UX-02, UX-03) +if [[ "$SKIP_AUDIT" != true && "$DRY_RUN" != true ]]; then + print_audit + + # TTY check (D-06) + if [[ -t 0 ]]; then + read -r -p "Proceed? [Y/n] " response < /dev/tty 2>&1 + response="${response,,}" # lowercase + if [[ "$response" == "n" || "$response" == "no" ]]; then + echo "Aborted." >&2 + exit 1 + fi + else + echo "${RED}Error: stdin is not a terminal. Pass --yes or -y to skip confirmation.${RESET}" >&2 + exit 1 + fi +fi +``` + +Key details: +- `Proceed? [Y/n]` -- default is proceed, Enter launches (D-05) +- Only `n` or `no` aborts. Any other input (including empty/Enter) proceeds. +- Non-TTY stdin aborts with actionable error message (D-06) +- `read -r -p` with `< /dev/tty` to handle stdin being consumed by pipes +- `${response,,}` lowercases the input for case-insensitive comparison +- The `2>&1` on read sends the prompt text to wherever stdout goes (which combined with `< /dev/tty` reads from terminal) + +Wait -- the prompt from `read -p` goes to stderr by default in some shells but stdout in bash. To ensure the prompt goes to stderr per D-07, use: + +```bash +echo -n "Proceed? [Y/n] " >&2 +read -r response < /dev/tty +``` + +This is cleaner and guarantees stderr for the prompt. + + + cd /home/toph/code/tools/claudebox && grep -q 'Proceed.*Y/n' claudebox.sh && grep -q '\-t 0' claudebox.sh && grep -q 'SKIP_AUDIT.*true' claudebox.sh && grep -q 'stdin is not a terminal' claudebox.sh && nix build 2>&1 && echo "PASS: confirmation prompt and nix build succeed" + + + - claudebox.sh contains `Proceed? [Y/n]` prompt text + - claudebox.sh checks `[[ -t 0 ]]` for TTY detection before prompting + - Non-TTY stdin prints error "stdin is not a terminal. Pass --yes or -y to skip confirmation." to stderr and exits 1 + - The prompt and "Aborted." message go to stderr (>&2) + - Typing `n` or `no` (case-insensitive) at the prompt exits 1 with "Aborted." + - Pressing Enter (empty input) proceeds with launch + - The audit+prompt block is guarded by `SKIP_AUDIT != true && DRY_RUN != true` + - `nix build` succeeds (shellcheck passes, full script valid) + - Complete script flow: parse flags -> --check early exit -> resolve binaries -> build env -> audit+prompt (unless --yes/--dry-run) -> --dry-run print -> exec bwrap + + Running `claudebox` shows env audit and prompts for confirmation. --yes/-y skips it. Non-TTY aborts with helpful error. nix build passes. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| host env -> audit display | Env var values displayed to stderr, masking required for secrets | +| user input -> read prompt | User response controls launch/abort decision | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-02-03 | Information Disclosure | Env audit displaying ANTHROPIC_API_KEY | mitigate | mask_value() auto-masks any var name matching *KEY*, *TOKEN*, *SECRET*, *PASSWORD*, *CREDENTIAL*. Shows first 7 + last 4 chars only. Values <= 11 chars show `***`. | +| T-02-04 | Information Disclosure | CLAUDEBOX_EXTRA_ENV secrets | mitigate | Same mask_value() applies to all displayed vars regardless of source category. User-added vars with sensitive names are masked. | +| T-02-05 | Elevation of Privilege | Non-interactive auto-proceed | mitigate | D-06: non-TTY stdin aborts with error, never auto-proceeds. Scripts/CI must explicitly pass --yes. | + + + +1. `nix build` succeeds (shellcheck + compilation) +2. `grep -c 'mask_value' claudebox.sh` returns >= 2 (definition + usage) +3. `grep 'Proceed' claudebox.sh` shows the prompt text +4. `grep 'SKIP_AUDIT' claudebox.sh` shows the guard condition +5. Script flow order: flag parsing -> --check -> binary resolution -> env construction -> audit arrays -> audit+prompt -> --dry-run -> exec bwrap + + + +- Env audit displays three grouped sections with colored headers to stderr +- PATH entries displayed one per line, indented +- Sensitive values auto-masked (ANTHROPIC_API_KEY shows `sk-ant-...xxxx`) +- Proceed? [Y/n] prompt with Enter=proceed, n=abort +- Non-interactive stdin aborts with actionable error +- --yes/-y skips entire audit+prompt +- --dry-run also skips audit+prompt +- nix build passes (shellcheck clean) + + + +After completion, create `.planning/phases/02-env-audit-and-cli-polish/02-02-SUMMARY.md` +