What v2.0 isolates, and what it doesn't
Per-project slices of aibox-home keep each project's home directory and full transcripts (~/.claude/projects) private. That holds up: a session in an empty project cannot reach another project's conversations, tool output, or the raw disk (verified on a real volume: default caps, no docker socket, seccomp/AppArmor, mknod'd device refused by the cgroup).
Two things stay common to every container, by construction, and the README says so:
-
~/.claude minus projects/ (the shared/claude-cfg slice), needed for one login and shared settings. Claude Code also keeps its bookkeeping there, for all projects at once:
history.jsonl — every prompt ever typed, tagged with project path and session id (on the reporting volume: ~12k prompts across 41 projects)
file-history/ — snapshots of files Claude edited, per session (~500 files, incl. personal documents from another project)
paste-cache/, todos/, tasks/, sessions/, session-env/, shell-snapshots/, backups/
.claude.json — every project path with per-project settings
So an agent in project A can read what was asked in project B and B's edit snapshots, but not B's conversations.
This slice is also an execution surface, not just readable bookkeeping. settings.json (hooks run arbitrary commands) and the user-level CLAUDE.md are consumed by every project's Claude and writable by every project's agent. A compromised project can therefore run commands in, or inject instructions into, every other project's container. The entrypoint no longer follows symlinks planted there (fixed pre-release), but the files themselves remain shared and writable.
-
~/.local/bin (the claude binary and its self-updates) is a shared, writable directory. A hostile or confused agent in one project can replace claude for every other project. Since c267093 it is last on PATH, so a planted git/node/sudo shim no longer shadows the real one, but the binary itself is still writable.
In short: v2.0 isolates projects' data from each other's ordinary sessions. It is not a defense against one project deliberately attacking another; the sandbox boundary that is defended is container-to-Mac.
Why not just slice more
The directories under 1 could be sliced like projects/ (nine more --mount lines, no migration). That would hide edit snapshots and pastes, but not prompts or project paths: history.jsonl and .claude.json are single files that Claude Code rewrites via temp-file + rename (.claude.json.tmp.<pid>.<hash> is visible on any volume), and rename onto a bind-mounted file fails with EBUSY. File-level mounts are therefore out, and the "mount a private ~/.claude and bind the three shared files on top" variant fails for the same reason, at the first credential refresh. Half a fix wasn't worth a second layout change days after the first, so 2.0 documents the sharing instead.
Options for a real fix (pick one, later)
- A. Private
~/.claude per project, login per project. Simplest mount layout (the claude-projects sub-mount goes away). Cost: claude login + onboarding once per project; settings/plugins/CLAUDE.md seeded from a template at slice creation. Full isolation, including prompts, hooks, and CLAUDE.md.
- B. Private
~/.claude per project, login injected. Same layout, but aibox holds one long-lived token from claude setup-token and passes it as CLAUDE_CODE_OAUTH_TOKEN to every container, so no credentials file is shared. Previously rejected as "credential injection"; listed for completeness. Needs a check that Claude Code accepts that token for Remote Control and doesn't rewrite .claude.json on logged-out runs (see the BlitzOS notes).
- C. Read-only shared binary. Mount
shared/local-bin and shared/claude-app read-only and let aibox update refresh the claude binary through a helper container. Closes 2 independently of 1.
Any of these changes the container run-spec, so it rides on the existing lazy recreation (containers are replaced only when idle).
Related: #3 (host-side overview page) — a per-project login state would also simplify what that page has to know.
What v2.0 isolates, and what it doesn't
Per-project slices of
aibox-homekeep each project's home directory and full transcripts (~/.claude/projects) private. That holds up: a session in an empty project cannot reach another project's conversations, tool output, or the raw disk (verified on a real volume: default caps, no docker socket, seccomp/AppArmor, mknod'd device refused by the cgroup).Two things stay common to every container, by construction, and the README says so:
~/.claudeminusprojects/(theshared/claude-cfgslice), needed for one login and shared settings. Claude Code also keeps its bookkeeping there, for all projects at once:history.jsonl— every prompt ever typed, tagged with project path and session id (on the reporting volume: ~12k prompts across 41 projects)file-history/— snapshots of files Claude edited, per session (~500 files, incl. personal documents from another project)paste-cache/,todos/,tasks/,sessions/,session-env/,shell-snapshots/,backups/.claude.json— every project path with per-project settingsSo an agent in project A can read what was asked in project B and B's edit snapshots, but not B's conversations.
This slice is also an execution surface, not just readable bookkeeping.
settings.json(hooks run arbitrary commands) and the user-levelCLAUDE.mdare consumed by every project's Claude and writable by every project's agent. A compromised project can therefore run commands in, or inject instructions into, every other project's container. The entrypoint no longer follows symlinks planted there (fixed pre-release), but the files themselves remain shared and writable.~/.local/bin(theclaudebinary and its self-updates) is a shared, writable directory. A hostile or confused agent in one project can replaceclaudefor every other project. Since c267093 it is last onPATH, so a plantedgit/node/sudoshim no longer shadows the real one, but the binary itself is still writable.In short: v2.0 isolates projects' data from each other's ordinary sessions. It is not a defense against one project deliberately attacking another; the sandbox boundary that is defended is container-to-Mac.
Why not just slice more
The directories under 1 could be sliced like
projects/(nine more--mountlines, no migration). That would hide edit snapshots and pastes, but not prompts or project paths:history.jsonland.claude.jsonare single files that Claude Code rewrites via temp-file + rename (.claude.json.tmp.<pid>.<hash>is visible on any volume), and rename onto a bind-mounted file fails with EBUSY. File-level mounts are therefore out, and the "mount a private~/.claudeand bind the three shared files on top" variant fails for the same reason, at the first credential refresh. Half a fix wasn't worth a second layout change days after the first, so 2.0 documents the sharing instead.Options for a real fix (pick one, later)
~/.claudeper project, login per project. Simplest mount layout (theclaude-projectssub-mount goes away). Cost:claudelogin + onboarding once per project; settings/plugins/CLAUDE.md seeded from a template at slice creation. Full isolation, including prompts, hooks, and CLAUDE.md.~/.claudeper project, login injected. Same layout, but aibox holds one long-lived token fromclaude setup-tokenand passes it asCLAUDE_CODE_OAUTH_TOKENto every container, so no credentials file is shared. Previously rejected as "credential injection"; listed for completeness. Needs a check that Claude Code accepts that token for Remote Control and doesn't rewrite.claude.jsonon logged-out runs (see the BlitzOS notes).shared/local-binandshared/claude-appread-only and letaibox updaterefresh the claude binary through a helper container. Closes 2 independently of 1.Any of these changes the container run-spec, so it rides on the existing lazy recreation (containers are replaced only when idle).
Related: #3 (host-side overview page) — a per-project login state would also simplify what that page has to know.