Skip to content

Cross-project isolation: Claude's shared bookkeeping and the shared claude binary dir #5

Description

@repalash

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:

  1. ~/.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.

  2. ~/.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions