- Replace --bind ~/.claudebox + --symlink with direct --bind ~/.claude ~/.claude - Add compute_canonical_root() function using git rev-parse --git-common-dir - Add per-project INSTANCE_DIR via sha256sum[:16] of canonical git root - Overlay projects/ with per-project hash dir for isolated conversation history - Overlay history.jsonl and SANDBOX.md as file-level bind mounts - Update credential mount target from ~/.claudebox to ~/.claude - Add CLAUDE_JSON_FILE (~/.claude.json) detection and conditional bind mount - Remove stale CLAUDE.md injection logic (D-06: user's real CLAUDE.md used) - Update dry-run block and print_audit to reflect new mount layout - Update SANDBOX.md heredoc to remove ~/.claudebox reference
325 lines
13 KiB
Markdown
325 lines
13 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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).
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
|
|
<interfaces>
|
|
<!-- From Plan 01: SKIP_AUDIT variable is set by flag parser (true when --yes/-y passed) -->
|
|
<!-- From Plan 01: DRY_RUN variable (dry-run implies skip audit) -->
|
|
<!-- ENV_ARGS array contains --setenv key value triplets for bwrap -->
|
|
<!-- HOST_ALLOWLIST array lists host-passed var names -->
|
|
<!-- CLAUDEBOX_EXTRA_ENV parsing block handles comma-separated extras -->
|
|
<!-- SANDBOX_PATH contains the colon-separated PATH for inside sandbox -->
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Add parallel display arrays and env audit display function</name>
|
|
<files>claudebox.sh</files>
|
|
<read_first>claudebox.sh</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>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"</automated>
|
|
</verify>
|
|
<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>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Add confirmation prompt with TTY detection and wire audit into launch flow</name>
|
|
<files>claudebox.sh</files>
|
|
<read_first>claudebox.sh</read_first>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>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"</automated>
|
|
</verify>
|
|
<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>
|
|
<done>Running `claudebox` shows env audit and prompts for confirmation. --yes/-y skips it. Non-TTY aborts with helpful error. nix build passes.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<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>
|
|
|
|
<verification>
|
|
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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/02-env-audit-and-cli-polish/02-02-SUMMARY.md`
|
|
</output>
|