5.6 KiB
| phase | reviewed | depth | files_reviewed | files_reviewed_list | findings | status | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-env-audit-and-cli-polish | 2026-04-09T12:00:00Z | standard | 2 |
|
|
issues_found |
Phase 2: Code Review Report
Reviewed: 2026-04-09 Depth: standard Files Reviewed: 2 Status: issues_found
Summary
Reviewed claudebox.sh (298 lines, shell script) and flake.nix (49 lines, Nix expression). The shell script is well-structured with good separation of concerns (flag parsing, audit display, sandbox exec). The Nix flake is clean and idiomatic.
Key concerns: the CLAUDEBOX_EXTRA_ENV escape hatch allows passing arbitrary environment variables into the sandbox without any blocklist, which undermines the core security model. There are also several robustness issues around variable handling and the dry-run output not matching actual execution.
Critical Issues
CR-01: CLAUDEBOX_EXTRA_ENV allows smuggling secrets into sandbox
File: claudebox.sh:162-172
Issue: The CLAUDEBOX_EXTRA_ENV escape hatch accepts any variable name without validation. A user (or a parent process) could set CLAUDEBOX_EXTRA_ENV=SSH_AUTH_SOCK,GPG_AGENT_INFO,AWS_SECRET_ACCESS_KEY and those values would be injected into the sandbox. This directly violates the project's core constraint: "Secrets never enter the Claude Code environment." While mask_value hides sensitive values in the audit display, the actual values are still passed via --setenv. An attacker or misconfigured environment could bypass the entire allowlist model.
Fix: Add a blocklist check before accepting extra vars:
# Blocklist of vars that must never enter the sandbox
BLOCKED_VARS="SSH_AUTH_SOCK|SSH_AGENT_PID|GPG_AGENT_INFO|GPG_TTY|AWS_SECRET_ACCESS_KEY|AWS_SESSION_TOKEN|GITHUB_TOKEN|GH_TOKEN|TAILSCALE_.*"
for var in "${EXTRAS[@]}"; do
var="${var// /}"
if [[ -n "$var" ]] && [[ -v "$var" ]]; then
if [[ "$var" =~ ^($BLOCKED_VARS)$ ]]; then
echo "${RED}Blocked:${RESET} $var is not allowed in sandbox" >&2
continue
fi
ENV_ARGS+=(--setenv "$var" "${!var}")
AUDIT_EXTRA_KEYS+=("$var")
AUDIT_EXTRA_VALS[$var]="${!var}"
fi
done
Warnings
WR-01: Dry-run output diverges from actual bwrap invocation
File: claudebox.sh:240-272
Issue: The dry-run block manually reconstructs the bwrap command with echo statements (lines 242-270) rather than deriving it from the same data used by the actual exec bwrap call (lines 275-298). If a mount is added or changed in the real invocation, the dry-run output will silently become stale. The --bind /nix/var/nix mount and the --ro-bind /etc/nix mount are both present in both places today, but this is a maintenance hazard -- the two blocks must be kept in sync manually.
Fix: Build the bwrap args array once, then use it for both dry-run printing and actual execution:
BWRAP_ARGS=(
--clearenv
"${ENV_ARGS[@]}"
--tmpfs /
--proc /proc
# ... all mounts ...
-- "${SANDBOX_CMD[@]}"
)
if [[ "$DRY_RUN" == true ]]; then
printf 'bwrap'
printf ' %q' "${BWRAP_ARGS[@]}"
printf '\n'
exit 0
fi
exec bwrap "${BWRAP_ARGS[@]}"
WR-02: Temp file cleanup race with exec
File: claudebox.sh:108-109
Issue: Line 109 sets trap 'rm -f "$GITCONFIG_TMP"' EXIT to clean up the temp gitconfig. However, line 275 uses exec bwrap ... which replaces the current process -- the EXIT trap never fires. The temp file leaks on every successful run. This is not a security issue (the file contains only name/email), but it accumulates stale files in /tmp.
Fix: Either accept the leak (temp files are cleaned by the OS on reboot) and remove the misleading trap, or delete the file after bwrap starts by restructuring to not use exec:
# Option A: Remove the misleading trap, add a comment
GITCONFIG_TMP=$(mktemp)
# Note: leaked intentionally -- exec replaces process, trap won't fire.
# /tmp is cleaned on reboot. File contains only git name/email.
WR-03: Unquoted variables in dry-run echo statements
File: claudebox.sh:264-268
Issue: Several dry-run echo statements use unquoted shell variables: $HOME, $CWD. If these contain spaces (unlikely for HOME but possible for CWD), the dry-run output would be misleading -- it would show split words rather than the actual path. The real exec bwrap call properly quotes these on lines 293-297.
Fix:
echo " --tmpfs \"$HOME\" \\"
echo " --bind \"$HOME/.claudebox\" \"$HOME/.claude\" \\"
echo " --bind \"$CWD\" \"$CWD\" \\"
echo " --chdir \"$CWD\" \\"
Info
IN-01: Hardcoded system architecture in flake.nix
File: flake.nix:18
Issue: system = "x86_64-linux"; hardcodes the architecture. This prevents the flake from being used on aarch64-linux or other systems. This is fine if claudebox is strictly for the developer's NixOS desktop, but limits portability.
Fix: Use flake-utils or nixpkgs.lib.genAttrs to support multiple systems:
nixpkgs.lib.genAttrs ["x86_64-linux" "aarch64-linux"] (system: ...)
IN-02: pass variable in --check uses string comparison instead of boolean
File: claudebox.sh:23,31,56
Issue: The pass variable is set to the string true/false and compared with [[ "$pass" == true ]]. This works in bash but is a code smell -- it looks like a boolean but is a string. Minor readability concern, no functional impact given writeShellApplication ensures bash.
Fix: No change needed -- this is idiomatic bash. Noting for awareness only.
Reviewed: 2026-04-09 Reviewer: Claude (gsd-code-reviewer) Depth: standard