claudebox/.planning/phases/02-env-audit-and-cli-polish/02-02-PLAN.md

13 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
02-env-audit-and-cli-polish 02 execute 2
02-01
claudebox.sh
true
UX-01
UX-02
truths artifacts key_links
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
path provides contains
claudebox.sh Env audit display, masking, confirmation prompt mask_value|print_audit|Proceed
from to via pattern
claudebox.sh env audit claudebox.sh SKIP_AUDIT flag if SKIP_AUDIT
from to via pattern
claudebox.sh audit display ENV_ARGS array parallel display arrays populated during env construction 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).

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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):

# 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:

# 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:

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+=:

AUDIT_HOST_KEYS+=("$var")
AUDIT_HOST_VALS[$var]="${!var}"

For CLAUDEBOX_EXTRA_ENV vars, inside the existing extras loop, add after each ENV_ARGS+=:

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):

# 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" <acceptance_criteria> - 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) </acceptance_criteria> 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:

# 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:

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" <acceptance_criteria> - 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 </acceptance_criteria> Running claudebox shows env audit and prompts for confirmation. --yes/-y skips it. Non-TTY aborts with helpful error. nix build passes.

<threat_model>

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.
</threat_model>
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

<success_criteria>

  • 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) </success_criteria>
After completion, create `.planning/phases/02-env-audit-and-cli-polish/02-02-SUMMARY.md`