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