13 KiB
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.
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:
- If DRY_RUN is true, skip audit (it implies --yes)
- 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
nornoaborts. Any other input (including empty/Enter) proceeds. - Non-TTY stdin aborts with actionable error message (D-06)
read -r -pwith< /dev/ttyto handle stdin being consumed by pipes${response,,}lowercases the input for case-insensitive comparison- The
2>&1on read sends the prompt text to wherever stdout goes (which combined with< /dev/ttyreads 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> |
<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>