# Parse claudebox flags SKIP_AUDIT=false DRY_RUN=false CHECK_MODE=false SHELL_MODE=false GC_MODE=false CLAUDE_ARGS=() # Config / harness globals (set by config files; CLI overrides applied after config loading) HARNESS_CMD="" # set by config or --cmd; empty means "use default claude" MOUNT_HOME=() # array of subdir names (relative to $HOME) PATH_ADD=() # array of dirs to prepend to sandbox PATH CONFIG_FILES_LOADED=() # for audit: list of loaded config paths # CLI override captures (applied on top of config after loading) CLI_HARNESS_CMD="" CLI_MOUNT_HOME=() CLI_PATH_ADD=() while (( $# > 0 )); do case "$1" in --yes|-y) SKIP_AUDIT=true ;; --dry-run) DRY_RUN=true ;; --check) CHECK_MODE=true ;; --shell) SHELL_MODE=true ;; --gc) GC_MODE=true ;; --cmd) [[ -z "${2:-}" ]] && { echo "Error: --cmd requires a binary name" >&2; exit 1; } CLI_HARNESS_CMD="$2"; shift ;; --mount-home) [[ -z "${2:-}" ]] && { echo "Error: --mount-home requires a subdir name" >&2; exit 1; } CLI_MOUNT_HOME+=("$2"); shift ;; --path-add) [[ -z "${2:-}" ]] && { echo "Error: --path-add requires a directory" >&2; exit 1; } CLI_PATH_ADD+=("${2/#\~/$HOME}"); shift ;; --) shift; CLAUDE_ARGS+=("$@"); break ;; *) CLAUDE_ARGS+=("$1") ;; esac shift done export SKIP_AUDIT # consumed by Plan 02 audit display # Compute canonical project root — worktree-aware (D-08, INST-02) # Defined here (near top) so it can be used in --check mode and config loading. compute_canonical_root() { local cwd="$1" local git_common git_common=$(git -C "$cwd" rev-parse --git-common-dir 2>/dev/null) || { echo "$cwd" return } # git returns relative ".git" for normal repos; make absolute if [[ "$git_common" != /* ]]; then git_common="$cwd/$git_common" fi dirname "$(readlink -f "$git_common")" } # Config file loader — KEY = VALUE format, blank/# lines ignored load_config_file() { local file="$1" [[ -f "$file" ]] || return 0 CONFIG_FILES_LOADED+=("$file") while IFS= read -r line || [[ -n "$line" ]]; do # ltrim line="${line#"${line%%[! ]*}"}" [[ -z "$line" || "$line" == '#'* ]] && continue [[ "$line" != *=* ]] && continue local key="${line%%=*}" local val="${line#*=}" # trim surrounding whitespace from key and val key="${key%"${key##*[! ]}"}"; key="${key#"${key%%[! ]*}"}" val="${val#"${val%%[! ]*}"}"; val="${val%"${val##*[! ]}"}" case "$key" in cmd) HARNESS_CMD="$val" ;; mount_home) MOUNT_HOME+=("$val") ;; path_add) PATH_ADD+=("${val/#\~/$HOME}") ;; *) echo "Warning: unknown key '$key' in $file" >&2 ;; esac done < "$file" } # Garbage-collect stale instance directories (D-11, INST-04) gc_instances() { local removed=0 local projects_dir="$HOME/.claudebox/projects" if [[ ! -d "$projects_dir" ]]; then echo "No projects directory found at $projects_dir" >&2 return fi for dir in "$projects_dir"/*/; do [[ -d "$dir" ]] || continue local root_file="$dir/project-root" [[ -f "$root_file" ]] || continue local root_path root_path=$(< "$root_file") if [[ ! -d "$root_path" ]]; then rm -rf "$dir" echo "Removed: $dir (project root gone: $root_path)" >&2 (( removed++ )) || true fi done echo "GC complete: $removed instance(s) removed." >&2 } # --check: verify prerequisites and exit (D-10, UX-05) if [[ "$CHECK_MODE" == true ]]; then pass=true green=$'\033[32m' red=$'\033[31m' yellow=$'\033[33m' reset=$'\033[0m' check_cmd() { if command -v "$1" &>/dev/null; then echo "${green}OK${reset} $1" >&2 else echo "${red}FAIL${reset} $1 -- not found" >&2 pass=false fi } echo "claudebox prerequisites:" >&2 echo "" >&2 check_cmd bwrap check_cmd claude check_cmd git check_cmd curl check_cmd nix if [[ -d "$HOME/.claudebox" ]]; then echo "${green}OK${reset} ~/.claudebox exists" >&2 else echo "${red}FAIL${reset} ~/.claudebox -- not found (will be created on first run)" >&2 fi if [[ -f "$HOME/.claudebox/config" ]]; then echo "${green}OK${reset} ~/.claudebox/config exists" >&2 else echo "${yellow}WARN${reset} ~/.claudebox/config -- not found (optional)" >&2 fi _proj_cfg=$(compute_canonical_root "$PWD")/.claudebox if [[ -f "$_proj_cfg" ]]; then echo "${green}OK${reset} $_proj_cfg exists" >&2 else echo "${yellow}WARN${reset} $_proj_cfg -- not found (optional)" >&2 fi unset _proj_cfg if [[ -v ANTHROPIC_API_KEY ]]; then echo "${green}OK${reset} ANTHROPIC_API_KEY is set" >&2 else echo "${yellow}WARN${reset} ANTHROPIC_API_KEY is not set" >&2 fi echo "" >&2 if [[ "$pass" == true ]]; then echo "${green}All checks passed.${reset}" >&2 exit 0 else echo "${red}Some checks failed.${reset}" >&2 exit 1 fi fi # --gc: remove stale instance directories and exit (D-12, INST-04) if [[ "$GC_MODE" == true ]]; then gc_instances exit 0 fi # 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 } # SANDBOX_PATH is injected by flake.nix via makeBinPath (only runtimeInputs, no host PATH) # Resolve binary paths from runtimeInputs SANDBOX_BASH="$(command -v bash)" CLAUDE_BIN="$(command -v claude)" # Record CWD CWD=$(pwd) # Ensure ~/.claudebox exists mkdir -p "$HOME/.claudebox" # Per-project instance isolation (D-04, D-07, D-09, D-10, INST-01) CANONICAL_ROOT=$(compute_canonical_root "$CWD") INSTANCE_HASH=$(printf '%s' "$CANONICAL_ROOT" | sha256sum | cut -c1-16) INSTANCE_DIR="$HOME/.claudebox/projects/$INSTANCE_HASH" mkdir -p "$INSTANCE_DIR" if [[ ! -f "$INSTANCE_DIR/project-root" ]]; then printf '%s\n' "$CANONICAL_ROOT" > "$INSTANCE_DIR/project-root" fi # Ensure history.jsonl source exists — bwrap bind requires source to exist (D-04) touch "$HOME/.claudebox/history.jsonl" # Load config files — CANONICAL_ROOT is now available (cascade: global then per-project) load_config_file "$HOME/.claudebox/config" load_config_file "$CANONICAL_ROOT/.claudebox" # Apply CLI overrides on top of config (CLI wins for scalar, appends for arrays) [[ -n "$CLI_HARNESS_CMD" ]] && HARNESS_CMD="$CLI_HARNESS_CMD" (( ${#CLI_MOUNT_HOME[@]} > 0 )) && MOUNT_HOME+=("${CLI_MOUNT_HOME[@]}") (( ${#CLI_PATH_ADD[@]} > 0 )) && PATH_ADD+=("${CLI_PATH_ADD[@]}") # Credential file mount (AUTH-01, AUTH-02) # Credential file lives in ~/.claudebox on the host; mounted into sandbox at ~/.claude/.credentials.json CREDS_FILE="$HOME/.claudebox/.credentials.json" if [[ -f "$CREDS_FILE" ]]; then CREDS_MOUNT=true else CREDS_MOUNT=false fi # Claude Code config file mount (~/.claude.json) # Stores auth tokens and user preferences; must be read-write so Claude Code # can update tokens and write backups without prompting for re-auth. CLAUDE_JSON_FILE="$HOME/.claude.json" if [[ -f "$CLAUDE_JSON_FILE" ]]; then CLAUDE_JSON_MOUNT=true else CLAUDE_JSON_MOUNT=false fi # === Sandbox-aware prompting (AWARE-01, AWARE-02) === # Write SANDBOX.md -- fully managed, overwritten every launch (D-02) cat > "$HOME/.claudebox/SANDBOX.md" << 'SANDBOXEOF' # Sandbox Environment You are running inside a bubblewrap (bwrap) sandbox managed by claudebox. Your filesystem is isolated -- only the current working directory and essential system paths are mounted. Your ~/.claude directory is bind-mounted from the host, with per-project isolation for conversation history. ## Installing Tools You have two ways to install tools on the fly: **Comma (preferred for quick one-off commands):** `, ripgrep` runs ripgrep without permanent installation. Comma uses nix-index to find the right package automatically. **Nix shell (for persistent access within the session):** `nix shell nixpkgs#python3 -c python3 script.py` runs a command with a package available. To keep it in your PATH for the session: `nix shell nixpkgs#python3` then use `python3` normally. ## Default Restrictions By default, the following are not mounted into the sandbox: - SSH keys (~/.ssh) - GPG and age keys (~/.gnupg, age key files) - Cloud credentials (~/.aws, ~/.config/gcloud) - Tailscale state If your setup has been customized, some of these may be available. ## Git Your git identity (name and email) is pre-configured from the host. The `safe.directory` setting trusts the mounted working directory. For remote operations, prefer HTTPS URLs over SSH since SSH keys are not available by default. SANDBOXEOF # Generate minimal .gitconfig (D-05) GIT_NAME=$(git config --global user.name 2>/dev/null || echo "Claude User") GIT_EMAIL=$(git config --global user.email 2>/dev/null || echo "claude@localhost") GITCONFIG_TMP=$(mktemp) trap 'rm -f "$GITCONFIG_TMP"' EXIT cat > "$GITCONFIG_TMP" </dev/null)" || { echo "${RED}Error: configured cmd '$HARNESS_CMD' not found in PATH${RESET}" >&2 exit 1 } else HARNESS_BIN="$CLAUDE_BIN" HARNESS_CMD="claude" fi IS_DEFAULT_CLAUDE=false [[ "$HARNESS_BIN" == "$CLAUDE_BIN" ]] && IS_DEFAULT_CLAUDE=true # Prepend PATH_ADD entries to SANDBOX_PATH before ENV_ARGS is built (HARNESS-03) if (( ${#PATH_ADD[@]} > 0 )); then _path_prefix="" for _p in "${PATH_ADD[@]}"; do _path_prefix+="${_p}:" done SANDBOX_PATH="${_path_prefix}${SANDBOX_PATH}" unset _path_prefix _p fi # 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=() # Build environment --setenv args array (D-03, D-04, SAND-02, SAND-03) # Sandbox-generated vars -- set directly, never from host ENV_ARGS=( --setenv HOME "$HOME" --setenv USER "$USER" --setenv PATH "$SANDBOX_PATH" --setenv SHELL "$SANDBOX_BASH" --setenv TMPDIR /tmp --setenv XDG_RUNTIME_DIR /tmp ) # Populate sandbox audit data AUDIT_SANDBOX_KEYS=(HOME USER PATH SHELL TMPDIR XDG_RUNTIME_DIR) 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" # SSL cert path: resolve to real nix store path so symlinks work inside the sandbox. # On NixOS, /etc/ssl/certs/ca-certificates.crt -> /etc/static/ssl/... -> /nix/store/... # The sandbox mounts /nix/store but not /etc/static, so we must resolve before entering. _SSL_CERT_DEFAULT="/etc/ssl/certs/ca-certificates.crt" _NIX_SSL_CERT="${NIX_SSL_CERT_FILE:-$_SSL_CERT_DEFAULT}" _NIX_SSL_CERT="$(readlink -f "$_NIX_SSL_CERT" 2>/dev/null || echo "$_NIX_SSL_CERT")" _SSL_CERT="${SSL_CERT_FILE:-$_NIX_SSL_CERT}" _SSL_CERT="$(readlink -f "$_SSL_CERT" 2>/dev/null || echo "$_SSL_CERT")" ENV_ARGS+=( --setenv NIX_SSL_CERT_FILE "$_NIX_SSL_CERT" --setenv SSL_CERT_FILE "$_SSL_CERT" ) AUDIT_SANDBOX_KEYS+=(NIX_SSL_CERT_FILE SSL_CERT_FILE) AUDIT_SANDBOX_VALS[NIX_SSL_CERT_FILE]="$_NIX_SSL_CERT" AUDIT_SANDBOX_VALS[SSL_CERT_FILE]="$_SSL_CERT" # Allowlisted host vars -- only pass if set on host HOST_ALLOWLIST=(TERM EDITOR LANG LC_ALL ANTHROPIC_API_KEY) for var in "${HOST_ALLOWLIST[@]}"; do if [[ -v "$var" ]]; then ENV_ARGS+=(--setenv "$var" "${!var}") AUDIT_HOST_KEYS+=("$var") AUDIT_HOST_VALS[$var]="${!var}" fi done # CLAUDEBOX_EXTRA_ENV escape hatch (D-03, comma-separated) if [[ -v CLAUDEBOX_EXTRA_ENV ]]; then IFS=',' read -ra EXTRAS <<< "$CLAUDEBOX_EXTRA_ENV" for var in "${EXTRAS[@]}"; do var="${var// /}" # trim whitespace if [[ -n "$var" ]] && [[ -v "$var" ]]; then ENV_ARGS+=(--setenv "$var" "${!var}") AUDIT_EXTRA_KEYS+=("$var") AUDIT_EXTRA_VALS[$var]="${!var}" fi done fi # Env files: ~/.claudebox/env (global) and /.claudebox.env (per-project) # Format: KEY=VALUE lines; blank lines and lines starting with # are ignored. load_env_file() { local file="$1" [[ -f "$file" ]] || return 0 while IFS= read -r line || [[ -n "$line" ]]; do # strip leading whitespace, skip blanks and comments line="${line#"${line%%[! ]*}"}" [[ -z "$line" || "$line" == '#'* ]] && continue # require KEY=VALUE form [[ "$line" != *=* ]] && continue local key="${line%%=*}" local val="${line#*=}" # strip optional surrounding quotes from value if [[ "$val" == '"'*'"' || "$val" == "'"*"'" ]]; then val="${val:1:${#val}-2}" fi ENV_ARGS+=(--setenv "$key" "$val") AUDIT_EXTRA_KEYS+=("$key") AUDIT_EXTRA_VALS[$key]="$val" done < "$file" } load_env_file "$HOME/.claudebox/env" load_env_file "$CANONICAL_ROOT/.claudebox.env" # Env audit display (D-01, D-02, D-03, D-04, D-07, UX-01) print_audit() { # Config section — shown when config files were loaded or a non-default harness is active if (( ${#CONFIG_FILES_LOADED[@]} > 0 )) || [[ "$IS_DEFAULT_CLAUDE" != true ]]; then echo "${BOLD}${CYAN}=== Config ===${RESET}" >&2 for _cf in "${CONFIG_FILES_LOADED[@]}"; do echo " loaded: $_cf" >&2 done echo " cmd=$HARNESS_CMD ($HARNESS_BIN)" >&2 (( ${#MOUNT_HOME[@]} > 0 )) && echo " mount_home: ${MOUNT_HOME[*]}" >&2 (( ${#PATH_ADD[@]} > 0 )) && echo " path_add: ${PATH_ADD[*]}" >&2 echo "" >&2 unset _cf fi echo "${BOLD}${CYAN}=== Sandbox Environment ===${RESET}" >&2 echo "" >&2 # Unified env list: sandbox [~], host allowlisted [>], extra [+] (D-06, D-07, D-08, D-09, D-10) for var in "${AUDIT_SANDBOX_KEYS[@]}"; do if [[ "$var" == "PATH" ]]; then echo " ${GREEN}[~]${RESET} PATH=" >&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}[~]${RESET} ${var}=$(mask_value "$var" "${AUDIT_SANDBOX_VALS[$var]}")" >&2 fi done for var in "${AUDIT_HOST_KEYS[@]}"; do echo " ${YELLOW}[>]${RESET} ${var}=$(mask_value "$var" "${AUDIT_HOST_VALS[$var]}")" >&2 done for var in "${AUDIT_EXTRA_KEYS[@]}"; do echo " ${CYAN}[+]${RESET} ${var}=$(mask_value "$var" "${AUDIT_EXTRA_VALS[$var]}")" >&2 done echo "" >&2 # Mounts section echo "${BOLD}Mounts:${RESET}" >&2 printf ' %-12s %s (read-write)\n' "CWD" "$CWD" >&2 printf ' %-12s %s (read-write)\n' "$HOME/.claude" "$HOME/.claude" >&2 printf ' %-12s %s (read-write, project: %s)\n' "projects/" "$INSTANCE_DIR" "$CANONICAL_ROOT" >&2 printf ' %-12s %s (read-write)\n' "history" "$HOME/.claudebox/history.jsonl" >&2 printf ' %-12s %s (read-only overlay)\n' "SANDBOX.md" "$HOME/.claudebox/SANDBOX.md" >&2 if [[ "$CREDS_MOUNT" == true ]]; then printf ' %-12s %s (read-write)\n' "credentials" "$CREDS_FILE" >&2 fi for _sub in "${MOUNT_HOME[@]}"; do _src="$HOME/$_sub" [[ -e "$_src" ]] || continue printf ' %-12s %s (read-write, mount_home)\n' "home" "$_src" >&2 done unset _sub _src echo "" >&2 # Network section (Phase 4 placeholder — full isolation comes in Phase 6) echo "${BOLD}Network:${RESET}" >&2 echo " full (host network)" >&2 } # 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 echo -n "Proceed? [Y/n] " >&2 read -r response < /dev/tty 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 # Build sandbox command if [[ "$SHELL_MODE" == true ]]; then SANDBOX_CMD=("$SANDBOX_BASH" "${CLAUDE_ARGS[@]}") elif [[ "$IS_DEFAULT_CLAUDE" == true ]]; then SANDBOX_CMD=("$HARNESS_BIN" --dangerously-skip-permissions "${CLAUDE_ARGS[@]}") else SANDBOX_CMD=("$HARNESS_BIN" "${CLAUDE_ARGS[@]}") fi # --dry-run: print the bwrap command without executing (D-09, UX-04) if [[ "$DRY_RUN" == true ]]; then { echo "bwrap \\" echo " --clearenv \\" # Guard: ENV_ARGS must be a multiple of 3 (--setenv NAME VALUE triplets) if (( ${#ENV_ARGS[@]} % 3 != 0 )); then echo "BUG: ENV_ARGS length ${#ENV_ARGS[@]} is not a multiple of 3" >&2 exit 1 fi dry_run_i=0 while (( dry_run_i < ${#ENV_ARGS[@]} )); do printf ' %s %s %q \\\n' "${ENV_ARGS[$dry_run_i]}" "${ENV_ARGS[$((dry_run_i+1))]}" "${ENV_ARGS[$((dry_run_i+2))]}" dry_run_i=$(( dry_run_i + 3 )) done echo " --tmpfs / \\" echo " --proc /proc \\" echo " --dev /dev \\" echo " --tmpfs /tmp \\" echo " --ro-bind /nix/store /nix/store \\" echo " --bind /nix/var/nix /nix/var/nix \\" echo " --ro-bind /etc/resolv.conf /etc/resolv.conf \\" echo " --ro-bind /etc/ssl /etc/ssl \\" echo " --ro-bind /etc/passwd /etc/passwd \\" echo " --ro-bind /etc/group /etc/group \\" echo " --ro-bind /etc/hosts /etc/hosts \\" echo " --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf \\" echo " --ro-bind /etc/nix /etc/nix \\" printf ' --symlink %q /usr/bin/env \\\n' "$(readlink -f "$(command -v env)")" printf ' --symlink %q /bin/sh \\\n' "$(readlink -f "$(command -v bash)")" echo " --tmpfs $HOME \\" echo " --bind $HOME/.claude $HOME/.claude \\" echo " --bind $INSTANCE_DIR $HOME/.claude/projects \\" echo " --bind $HOME/.claudebox/history.jsonl $HOME/.claude/history.jsonl \\" echo " --bind $HOME/.claudebox/SANDBOX.md $HOME/.claude/SANDBOX.md \\" if [[ "$CLAUDE_JSON_MOUNT" == true ]]; then echo " --bind $CLAUDE_JSON_FILE $HOME/.claude.json \\" fi if [[ "$CREDS_MOUNT" == true ]]; then echo " --bind $CREDS_FILE $HOME/.claude/.credentials.json \\" fi for _dry_sub in "${MOUNT_HOME[@]}"; do _dry_src="$HOME/$_dry_sub" [[ -e "$_dry_src" ]] || continue echo " --bind $_dry_src $_dry_src \\" done unset _dry_sub _dry_src printf ' --ro-bind %q %s/.gitconfig \\\n' "$GITCONFIG_TMP" "$HOME" echo " --bind $CWD $CWD \\" echo " --chdir $CWD \\" printf ' -- %s\n' "${SANDBOX_CMD[*]}" } >&2 exit 0 fi # Build bwrap mount args array (allows conditional mounts) BWRAP_ARGS=( --clearenv "${ENV_ARGS[@]}" --tmpfs / --proc /proc --dev /dev --tmpfs /tmp --ro-bind /nix/store /nix/store --bind /nix/var/nix /nix/var/nix --ro-bind /etc/resolv.conf /etc/resolv.conf --ro-bind /etc/ssl /etc/ssl --ro-bind /etc/passwd /etc/passwd --ro-bind /etc/group /etc/group --ro-bind /etc/hosts /etc/hosts --ro-bind /etc/nsswitch.conf /etc/nsswitch.conf --ro-bind /etc/nix /etc/nix --symlink "$(readlink -f "$(command -v env)")" /usr/bin/env --symlink "$(readlink -f "$(command -v bash)")" /bin/sh --tmpfs "$HOME" # Phase 5: direct ~/.claude bind (D-01) — all plugins/skills/hooks/MCP visible --bind "$HOME/.claude" "$HOME/.claude" # Phase 5: overlay projects/ with per-project isolated dir (D-02, INST-01) --bind "$INSTANCE_DIR" "$HOME/.claude/projects" # Phase 5: overlay history.jsonl with sandbox-side file (D-03) --bind "$HOME/.claudebox/history.jsonl" "$HOME/.claude/history.jsonl" # Phase 5: inject SANDBOX.md as file overlay (D-06) --bind "$HOME/.claudebox/SANDBOX.md" "$HOME/.claude/SANDBOX.md" ) if [[ "$CLAUDE_JSON_MOUNT" == true ]]; then BWRAP_ARGS+=(--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json") fi if [[ "$CREDS_MOUNT" == true ]]; then BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json") fi for _sub in "${MOUNT_HOME[@]}"; do _src="$HOME/$_sub" if [[ ! -e "$_src" ]]; then echo "${YELLOW}Warning: mount_home '$_sub' does not exist at $_src; skipping${RESET}" >&2 continue fi BWRAP_ARGS+=(--bind "$_src" "$_src") done unset _sub _src BWRAP_ARGS+=( --ro-bind "$GITCONFIG_TMP" "$HOME/.gitconfig" --bind "$CWD" "$CWD" --chdir "$CWD" -- "${SANDBOX_CMD[@]}" ) # exec bwrap (SAND-04 through SAND-15, UX-06, D-01) exec bwrap "${BWRAP_ARGS[@]}"