421 lines
21 KiB
Markdown
421 lines
21 KiB
Markdown
---
|
|
phase: 05-per-project-instance-isolation
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- claudebox.sh
|
|
- .planning/REQUIREMENTS.md
|
|
autonomous: false
|
|
requirements:
|
|
- INST-01
|
|
- INST-02
|
|
- INST-03
|
|
must_haves:
|
|
truths:
|
|
- "Launching claudebox in project A produces conversation history isolated from project B"
|
|
- "Launching claudebox from a git worktree shares instance state with the main worktree"
|
|
- "All Claude Code plugins, skills, hooks, MCP configs, commands, and settings are visible inside the sandbox"
|
|
- "Two concurrent sessions in the same project share the same instance dir without corruption"
|
|
- "CLAUDE_JSON_FILE mount for ~/.claude.json is preserved after Phase 5 changes"
|
|
artifacts:
|
|
- path: "claudebox.sh"
|
|
provides: "compute_canonical_root function, instance initialization, new mount layout"
|
|
contains: "compute_canonical_root"
|
|
- path: "claudebox.sh"
|
|
provides: "BWRAP_ARGS with direct ~/.claude bind and overlay mounts"
|
|
contains: "--bind \"$HOME/.claude\" \"$HOME/.claude\""
|
|
- path: "claudebox.sh"
|
|
provides: "CLAUDE_JSON_FILE conditional mount preserved"
|
|
contains: "--bind \"$CLAUDE_JSON_FILE\" \"$HOME/.claude.json\""
|
|
- path: ".planning/REQUIREMENTS.md"
|
|
provides: "INST-01 through INST-04 requirement definitions"
|
|
contains: "INST-04"
|
|
key_links:
|
|
- from: "claudebox.sh (compute_canonical_root)"
|
|
to: "INSTANCE_DIR variable"
|
|
via: "sha256sum of canonical root path"
|
|
pattern: "sha256sum.*cut -c1-16"
|
|
- from: "claudebox.sh (BWRAP_ARGS)"
|
|
to: "bwrap execution"
|
|
via: "overlay mounts after parent bind"
|
|
pattern: "--bind.*\\.claude/projects"
|
|
---
|
|
|
|
<objective>
|
|
Rewrite claudebox mount architecture from symlink-based to direct bind + overlay, implement per-project instance isolation, and register INST-01 through INST-04 requirements.
|
|
|
|
Purpose: Fix plugin/skill/hook visibility (all Claude Code config in ~/.claude becomes available) and scope conversation history per project directory so different projects never share state.
|
|
|
|
Output: Updated claudebox.sh with new mount layout, canonical root computation, per-project hash directories, and updated dry-run/audit display. REQUIREMENTS.md updated with INST-01 through INST-04.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/toph/code/tools/claudebox/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/toph/code/tools/claudebox/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/REQUIREMENTS.md
|
|
@.planning/phases/05-per-project-instance-isolation/05-CONTEXT.md
|
|
@.planning/phases/05-per-project-instance-isolation/05-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Current claudebox.sh structure the executor needs to understand -->
|
|
|
|
From claudebox.sh (flag parsing, lines 1-18):
|
|
```bash
|
|
SKIP_AUDIT=false
|
|
DRY_RUN=false
|
|
CHECK_MODE=false
|
|
SHELL_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 ;;
|
|
--) shift; CLAUDE_ARGS+=("$@"); break ;;
|
|
*) CLAUDE_ARGS+=("$1") ;;
|
|
esac
|
|
shift
|
|
done
|
|
```
|
|
|
|
From claudebox.sh (CLAUDE_JSON_FILE detection, lines 114-122 — MUST BE PRESERVED):
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
From claudebox.sh (BWRAP_ARGS, lines 364-398 — MUST BE REPLACED):
|
|
```bash
|
|
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
|
|
...
|
|
--tmpfs "$HOME"
|
|
--bind "$HOME/.claudebox" "$HOME/.claudebox" # OLD: remove
|
|
--symlink "$HOME/.claudebox" "$HOME/.claude" # OLD: remove
|
|
)
|
|
```
|
|
|
|
From claudebox.sh (CLAUDE_JSON conditional mount, lines 386-388 — MUST BE PRESERVED):
|
|
```bash
|
|
if [[ "$CLAUDE_JSON_MOUNT" == true ]]; then
|
|
BWRAP_ARGS+=(--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json")
|
|
fi
|
|
```
|
|
|
|
From claudebox.sh (credential mount, lines 104-112):
|
|
```bash
|
|
CREDS_FILE="$HOME/.claudebox/.credentials.json"
|
|
CLAUDE_JSON_FILE="$HOME/.claude.json"
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Rewrite mount architecture and add per-project isolation</name>
|
|
<files>claudebox.sh</files>
|
|
<read_first>
|
|
- claudebox.sh (entire file — current mount layout, credential logic, CLAUDE_JSON_FILE detection at lines 114-122, CLAUDE_JSON_MOUNT conditional at lines 386-388, SANDBOX.md generation, CLAUDE.md injection, audit display, dry-run block, BWRAP_ARGS)
|
|
- .planning/phases/05-per-project-instance-isolation/05-RESEARCH.md (verified patterns, mount order, anti-patterns, Pitfall 6 re CLAUDE_JSON)
|
|
- .planning/phases/05-per-project-instance-isolation/05-CONTEXT.md (locked decisions D-01 through D-14)
|
|
</read_first>
|
|
<action>
|
|
Modify claudebox.sh with these changes, in order:
|
|
|
|
**1. Add `compute_canonical_root` function** (insert after `CWD=$(pwd)` on line 99):
|
|
|
|
```bash
|
|
# 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")"
|
|
}
|
|
```
|
|
|
|
**2. Add instance initialization** (insert after the compute_canonical_root function):
|
|
|
|
```bash
|
|
# 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"
|
|
```
|
|
|
|
**3. Remove CLAUDE.md injection logic** (delete lines 166-174, the block starting with `# Ensure CLAUDE.md has @SANDBOX.md import`):
|
|
Delete the entire block:
|
|
```bash
|
|
CLAUDEMD="$HOME/.claudebox/CLAUDE.md"
|
|
if [[ ! -f "$CLAUDEMD" ]]; then
|
|
...
|
|
fi
|
|
```
|
|
Per D-06: user's real `~/.claude/CLAUDE.md` already has `@SANDBOX.md`. The SANDBOX.md file itself is still written to `~/.claudebox/SANDBOX.md` and will be bind-mounted as an overlay. The CLAUDE.md injection is no longer needed and would write to an invisible path.
|
|
|
|
**4. Replace BWRAP_ARGS mount section** (lines 382-384). Replace these three lines:
|
|
```bash
|
|
--bind "$HOME/.claudebox" "$HOME/.claudebox"
|
|
--symlink "$HOME/.claudebox" "$HOME/.claude"
|
|
```
|
|
With (per D-01, D-02, D-03, D-06):
|
|
```bash
|
|
# 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"
|
|
```
|
|
|
|
**5. Preserve CLAUDE_JSON_MOUNT conditional block** (currently lines 386-388). This block MUST remain after the BWRAP_ARGS array:
|
|
```bash
|
|
if [[ "$CLAUDE_JSON_MOUNT" == true ]]; then
|
|
BWRAP_ARGS+=(--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json")
|
|
fi
|
|
```
|
|
This mounts `~/.claude.json` (the root-level file, NOT inside `~/.claude/`). It is independent of the Phase 5 architecture changes. Verify this block is still present and functional after the BWRAP_ARGS rewrite. Per RESEARCH.md Pitfall 6, this was uncommitted from Phase 4 and must be incorporated.
|
|
|
|
**6. Update credential mount target** (in the conditional block after BWRAP_ARGS, currently line ~390):
|
|
Change:
|
|
```bash
|
|
BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claudebox/.credentials.json")
|
|
```
|
|
To:
|
|
```bash
|
|
BWRAP_ARGS+=(--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json")
|
|
```
|
|
Per Pitfall 3 in RESEARCH.md: the old target path `~/.claudebox/.credentials.json` no longer exists in the sandbox since `~/.claudebox` is no longer mounted.
|
|
|
|
**7. Update dry-run block** (lines 318-361). Replace lines 348-355 (the mount echo lines for claudebox bind, symlink, and credential target):
|
|
Replace:
|
|
```bash
|
|
echo " --bind $HOME/.claudebox $HOME/.claudebox \\"
|
|
echo " --symlink $HOME/.claudebox $HOME/.claude \\"
|
|
...
|
|
if [[ "$CREDS_MOUNT" == true ]]; then
|
|
echo " --bind $CREDS_FILE $HOME/.claudebox/.credentials.json \\"
|
|
fi
|
|
```
|
|
With:
|
|
```bash
|
|
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
|
|
```
|
|
Note: The CLAUDE_JSON dry-run echo MUST be preserved here as well.
|
|
|
|
**8. Update print_audit mounts section** (inside print_audit function, around line 276-283). Replace mount display lines:
|
|
Replace:
|
|
```bash
|
|
printf ' %-12s %s (read-write)\n' "$HOME/.claude" "$HOME/.claudebox" >&2
|
|
```
|
|
With:
|
|
```bash
|
|
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
|
|
```
|
|
|
|
**9. Update SANDBOX.md content** (the heredoc starting at line 127). Change the line:
|
|
```
|
|
Both ~/.claude and ~/.claudebox
|
|
point to the same directory inside the sandbox.
|
|
```
|
|
To:
|
|
```
|
|
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.
|
|
```
|
|
(Remove the `~/.claudebox` reference since it's no longer visible in the sandbox.)
|
|
</action>
|
|
<verify>
|
|
<automated>bash -n claudebox.sh && grep -q 'compute_canonical_root' claudebox.sh && grep -q 'INSTANCE_HASH' claudebox.sh && grep -q -- '--bind "$HOME/.claude" "$HOME/.claude"' claudebox.sh && grep -q -- '--bind "$INSTANCE_DIR" "$HOME/.claude/projects"' claudebox.sh && grep -q -- '--bind "$HOME/.claudebox/history.jsonl" "$HOME/.claude/history.jsonl"' claudebox.sh && grep -q -- '--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json"' claudebox.sh && grep -q -- '--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json"' claudebox.sh && ! grep -q -- '--symlink.*\.claudebox.*\.claude' claudebox.sh && ! grep -q -- '--bind "$HOME/.claudebox" "$HOME/.claudebox"' claudebox.sh && echo "ALL CHECKS PASSED"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- claudebox.sh passes `bash -n` syntax check
|
|
- claudebox.sh contains `compute_canonical_root()` function with `git rev-parse --git-common-dir` inside it
|
|
- claudebox.sh contains `INSTANCE_HASH=$(printf '%s' "$CANONICAL_ROOT" | sha256sum | cut -c1-16)`
|
|
- claudebox.sh contains `mkdir -p "$INSTANCE_DIR"`
|
|
- claudebox.sh contains `--bind "$HOME/.claude" "$HOME/.claude"` (D-01 direct bind)
|
|
- claudebox.sh contains `--bind "$INSTANCE_DIR" "$HOME/.claude/projects"` (D-02 overlay)
|
|
- claudebox.sh contains `--bind "$HOME/.claudebox/history.jsonl" "$HOME/.claude/history.jsonl"` (D-03 overlay)
|
|
- claudebox.sh contains `--bind "$HOME/.claudebox/SANDBOX.md" "$HOME/.claude/SANDBOX.md"` (D-06 overlay)
|
|
- claudebox.sh contains `--bind "$CREDS_FILE" "$HOME/.claude/.credentials.json"` (updated target)
|
|
- claudebox.sh contains `--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json"` (CLAUDE_JSON_MOUNT preserved per Pitfall 6)
|
|
- claudebox.sh does NOT contain `--symlink "$HOME/.claudebox" "$HOME/.claude"` (old symlink removed)
|
|
- claudebox.sh does NOT contain `--bind "$HOME/.claudebox" "$HOME/.claudebox"` (old bind removed)
|
|
- claudebox.sh does NOT contain `CLAUDEMD="$HOME/.claudebox/CLAUDE.md"` (injection removed)
|
|
- Dry-run block echoes `--bind $HOME/.claude $HOME/.claude` instead of old claudebox bind+symlink
|
|
- Dry-run block echoes `--bind $INSTANCE_DIR $HOME/.claude/projects`
|
|
- Dry-run block echoes `--bind $CLAUDE_JSON_FILE $HOME/.claude.json` when CLAUDE_JSON_MOUNT is true
|
|
- print_audit shows `projects/` mount line with `$INSTANCE_DIR` and `$CANONICAL_ROOT`
|
|
- SANDBOX.md heredoc does NOT contain `~/.claudebox`
|
|
- INST-03 satisfied by D-13: Claude Code manages own file concurrency; no locking mechanism needed in claudebox.sh. Two concurrent sessions share the same INSTANCE_DIR safely.
|
|
</acceptance_criteria>
|
|
<done>
|
|
claudebox.sh has new mount architecture (direct ~/.claude bind + overlays for projects/, history.jsonl, SANDBOX.md, credentials), per-project instance isolation via SHA-256 hash of canonical git root, CLAUDE_JSON_FILE mount preserved, and all display/dry-run blocks updated to match. Old symlink approach completely removed.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Register INST-01 through INST-04 in REQUIREMENTS.md</name>
|
|
<files>.planning/REQUIREMENTS.md</files>
|
|
<read_first>
|
|
- .planning/REQUIREMENTS.md (current content — ends at AUTH-02)
|
|
- .planning/phases/05-per-project-instance-isolation/05-RESEARCH.md (phase_requirements table with INST-01 through INST-04 descriptions)
|
|
- .planning/ROADMAP.md (Phase 5 success criteria for cross-reference)
|
|
</read_first>
|
|
<action>
|
|
Append the following section to `.planning/REQUIREMENTS.md`, after the `### Authentication Passthrough` section (after AUTH-02) and before the `### Network Isolation` section:
|
|
|
|
```markdown
|
|
### Instance Isolation
|
|
|
|
- **INST-01**: Each project directory has isolated conversation history — launching claudebox in two different project directories produces separate histories with no cross-contamination
|
|
- **INST-02**: Git worktrees of the same repo share instance state with the main worktree (canonical root resolved via `git rev-parse --git-common-dir`)
|
|
- **INST-03**: Two concurrent claudebox sessions in the same project do not corrupt each other's state (satisfied architecturally: Claude Code manages its own file-level concurrency within its data dir; no locking needed per D-13)
|
|
- **INST-04**: `claudebox --gc` removes instance directories for project roots that no longer exist on disk
|
|
```
|
|
|
|
Also update the Traceability table at the bottom of the file to add:
|
|
|
|
```markdown
|
|
| INST-01 | Phase 5 | Pending |
|
|
| INST-02 | Phase 5 | Pending |
|
|
| INST-03 | Phase 5 | Pending |
|
|
| INST-04 | Phase 5 | Pending |
|
|
```
|
|
|
|
And update the Coverage line to reflect the new count: `v1 requirements: 31 total, v2 requirements (partial): 6` (was 2, adding 4 INST requirements).
|
|
</action>
|
|
<verify>
|
|
<automated>grep -q 'INST-01' .planning/REQUIREMENTS.md && grep -q 'INST-02' .planning/REQUIREMENTS.md && grep -q 'INST-03' .planning/REQUIREMENTS.md && grep -q 'INST-04' .planning/REQUIREMENTS.md && grep -q 'Instance Isolation' .planning/REQUIREMENTS.md && grep -c 'INST-0' .planning/REQUIREMENTS.md | grep -q '[4-9]' && echo "ALL CHECKS PASSED"</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- .planning/REQUIREMENTS.md contains `### Instance Isolation` section header
|
|
- .planning/REQUIREMENTS.md contains `INST-01` with description mentioning "isolated conversation history"
|
|
- .planning/REQUIREMENTS.md contains `INST-02` with description mentioning "git worktrees" and "git rev-parse --git-common-dir"
|
|
- .planning/REQUIREMENTS.md contains `INST-03` with description mentioning "concurrent" and "D-13"
|
|
- .planning/REQUIREMENTS.md contains `INST-04` with description mentioning "--gc" and "project roots that no longer exist"
|
|
- Traceability table contains rows for INST-01 through INST-04 mapped to Phase 5
|
|
- `### Instance Isolation` section appears after `### Authentication Passthrough` and before `### Network Isolation`
|
|
</acceptance_criteria>
|
|
<done>
|
|
REQUIREMENTS.md contains INST-01 through INST-04 definitions with descriptions matching Phase 5 success criteria, traceability entries mapping all four to Phase 5, and updated coverage count.
|
|
</done>
|
|
</task>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
|
<name>Task 3: Verify mount architecture and per-project isolation</name>
|
|
<files>claudebox.sh</files>
|
|
<action>Human verifies the mount architecture rewrite works correctly end-to-end.</action>
|
|
<what-built>Complete mount architecture rewrite: direct ~/.claude bind with per-project overlay isolation. Dry-run and audit display updated. CLAUDE_JSON_FILE mount preserved.</what-built>
|
|
<how-to-verify>
|
|
1. Run `claudebox --dry-run` from this repo — verify output shows `--bind $HOME/.claude $HOME/.claude` followed by `--bind <hash-dir> $HOME/.claude/projects` (no `--symlink`, no `--bind ~/.claudebox ~/.claudebox`)
|
|
2. Run `claudebox --dry-run` from a different project dir — verify the INSTANCE_DIR path differs (different hash)
|
|
3. Run `ls ~/.claudebox/projects/` — verify a hash-named directory was created with a `project-root` file inside it
|
|
4. Run `cat ~/.claudebox/projects/*/project-root` — verify it contains the canonical project root path
|
|
5. Run `claudebox --shell -- ls ~/.claude/` and confirm plugins/, commands/, hooks/ or equivalent subdirs are visible
|
|
6. Run `claudebox --dry-run` and verify `--bind <CLAUDE_JSON_FILE> $HOME/.claude.json` line is present in the output
|
|
</how-to-verify>
|
|
<verify>Human confirms all 6 verification steps pass</verify>
|
|
<done>Mount architecture rewrite verified: direct ~/.claude bind works, per-project overlay produces isolated hash dirs, dry-run output is correct, CLAUDE_JSON mount preserved, Claude Code launches with plugins visible.</done>
|
|
<resume-signal>Type "approved" or describe issues</resume-signal>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| host filesystem to sandbox | bwrap bind mounts expose host paths inside sandbox; overlay order determines what's visible |
|
|
| user input (CWD) to hash computation | CWD is user-controlled; used as input to SHA-256 hash for instance dir path |
|
|
| project-root file to GC deletion | plaintext path stored in project-root file is trusted by GC to determine what to delete |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-05-01 | Tampering | compute_canonical_root | mitigate | Use `readlink -f` to resolve symlinks to real paths before hashing; prevents symlink-based hash collisions |
|
|
| T-05-02 | Information Disclosure | overlay mount order | mitigate | Overlay mounts MUST come AFTER parent `~/.claude` bind; bwrap last-mount-wins ensures isolated projects/ is what the sandbox sees, not the host's real projects/ |
|
|
| T-05-03 | Tampering | INSTANCE_DIR path construction | mitigate | INSTANCE_DIR is built from `$HOME/.claudebox/projects/$INSTANCE_HASH` where INSTANCE_HASH is hex-only output of sha256sum|cut; no path traversal possible via hash |
|
|
| T-05-04 | Information Disclosure | cross-project data | mitigate | Each project gets unique hash dir mounted as entire `~/.claude/projects/`; sandbox cannot see other projects' hash dirs |
|
|
| T-05-05 | Denial of Service | unbounded instance dirs | accept | Instance dirs accumulate until `--gc` is run; acceptable for personal tool; GC is Phase 5 Plan 02 |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
1. `bash -n claudebox.sh` passes (no syntax errors)
|
|
2. `claudebox --dry-run` shows new mount layout with no old symlink/claudebox references
|
|
3. `claudebox --dry-run` shows `--bind <CLAUDE_JSON_FILE> $HOME/.claude.json` line
|
|
4. Two different project dirs produce different INSTANCE_HASH values
|
|
5. `~/.claudebox/projects/<hash>/project-root` contains the correct canonical root
|
|
6. Claude Code launches successfully with plugins visible
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Mount architecture uses direct `~/.claude` bind with overlay mounts for projects/, history.jsonl, SANDBOX.md, and credentials
|
|
- Per-project isolation via SHA-256[:16] of canonical git root path
|
|
- Git worktrees resolve to same canonical root (via --git-common-dir)
|
|
- Old symlink approach completely removed from claudebox.sh
|
|
- CLAUDE_JSON_FILE mount (`--bind "$CLAUDE_JSON_FILE" "$HOME/.claude.json"`) preserved
|
|
- Dry-run and audit display reflect new mount layout
|
|
- INST-01 through INST-04 registered in REQUIREMENTS.md
|
|
- INST-03 satisfied architecturally by D-13 (documented in acceptance criteria)
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/05-per-project-instance-isolation/05-01-SUMMARY.md`
|
|
</output>
|