- ~/.claudebox/env and <project>/.claudebox.env loaded at launch - NIX_SSL_CERT_FILE passed from host instead of hardcoded path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
494 lines
16 KiB
Bash
494 lines
16 KiB
Bash
# Parse claudebox flags
|
|
SKIP_AUDIT=false
|
|
DRY_RUN=false
|
|
CHECK_MODE=false
|
|
SHELL_MODE=false
|
|
GC_MODE=false
|
|
CLAUDE_ARGS=()
|
|
|
|
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 ;;
|
|
--) shift; CLAUDE_ARGS+=("$@"); break ;;
|
|
*) CLAUDE_ARGS+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
export SKIP_AUDIT # consumed by Plan 02 audit display
|
|
|
|
# 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 [[ -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)
|
|
|
|
# Compute canonical project root — worktree-aware (D-08, INST-02)
|
|
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")"
|
|
}
|
|
|
|
# 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"
|
|
|
|
# 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" <<GITEOF
|
|
[user]
|
|
name = $GIT_NAME
|
|
email = $GIT_EMAIL
|
|
[safe]
|
|
directory = *
|
|
GITEOF
|
|
|
|
# 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: prefer host NIX_SSL_CERT_FILE (NixOS sets this to a nix store path);
|
|
# fall back to /etc/ssl/certs/ca-certificates.crt for non-NixOS hosts.
|
|
_SSL_CERT_DEFAULT="/etc/ssl/certs/ca-certificates.crt"
|
|
_NIX_SSL_CERT="${NIX_SSL_CERT_FILE:-$_SSL_CERT_DEFAULT}"
|
|
_SSL_CERT="${SSL_CERT_FILE:-$_NIX_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 <project>/.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() {
|
|
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
|
|
|
|
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[@]}")
|
|
else
|
|
SANDBOX_CMD=("$CLAUDE_BIN" --dangerously-skip-permissions "${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
|
|
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
|
|
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[@]}"
|