claudebox/.planning/research/FEATURES.md

197 lines
15 KiB
Markdown

# Feature Landscape
**Domain:** CLI sandbox wrapper (Nix/bubblewrap) for AI coding agents
**Researched:** 2026-04-10 (v2.0 milestone update — network isolation, profiles, auth passthrough)
**Confidence:** MEDIUM-HIGH (web-verified for auth files, slirp4netns mechanics, Claude Code storage layout; MEDIUM for devshell injection patterns)
---
## v1.0 Features (Already Built — Reference Only)
Core sandbox, env allowlist, secret path hiding, minimal PATH, pre-launch audit, comma/nix tool provisioning, SANDBOX.md injection, --check/--dry-run/--shell modes. Do not re-implement.
---
## v2.0 New Features Under Research
### Table Stakes for v2.0
Features required for the milestone to be considered complete. Without these, the milestone goals are unmet.
| Feature | Why Expected | Complexity | Notes |
|---------|--------------|------------|-------|
| **Host auth passthrough** | Without it, every sandbox launch requires re-authenticating Claude Code — subscriptions and API keys are unusable | LOW | Mount `~/.claude/.credentials.json` read-only into `~/.claudebox/.credentials.json`. On Linux, Claude stores auth in `~/.claude/.credentials.json` (mode 0600). A read-only bind mount passes the file into the sandbox; Claude Code reads it on startup. No write access needed — token refresh writes back to the same file, so the mount must permit writes or use a copy-on-launch approach. |
| **Per-project instance isolation** | Running claudebox in two different projects should not share conversation history, todos, or project settings. Currently both projects write to the same `~/.claudebox/projects/` keyed by encoded path — works but muddles state if `~/.claudebox` is wiped or shared | LOW-MEDIUM | Claude Code stores sessions at `~/.claude/projects/{path-encoded}/`. A per-project instance dir `~/.claudebox/instances/<hash>/` contains its own `.claude/`; bind-mount that instead of `~/.claudebox` directly. Hash = SHA1 or MD5 of CWD absolute path. Instance dir auto-created on first launch. |
| **Named profiles** | Users need repeatable configurations for different types of work (e.g., "web" profile with cloud creds, "offline" profile with no network) | MEDIUM | Profile = named config at `~/.claudebox/profiles/<name>.conf` (or `.toml`). Contains: extra env vars to pass, extra mount paths (ro or rw), extra Nix packages, network tier. Activated via `--profile foo` or `CLAUDEBOX_PROFILE=foo`. Merges with defaults; profile settings additive on top of base config. |
| **Tiered network isolation** | "full" (current, no restriction), "internet-only" (blocks LAN/Tailscale/localhost), "none" (fully offline) serve distinct use cases: internet-only prevents Claude from reaching internal services; none is for sensitive codebases | HIGH | Three tiers: `full` = current behavior (host network shared). `none` = `--unshare-net` in bwrap, no connectivity. `internet-only` = `--unshare-net` + slirp4netns as separate process providing NAT'd internet via TAP device (`--disable-host-loopback` to block LAN). This is the complex tier. |
| **Nix devshell injection** | Projects with `flake.nix` defining a devShell should be able to make those packages available to Claude inside the sandbox | MEDIUM-HIGH | Two viable approaches: (a) resolve devShell's `buildInputs` to store paths and add them to `PATH`/mounts before launch; (b) run `nix print-dev-env` to get env vars and merge them. Approach (b) is cleaner — `nix print-dev-env .#devShell` emits JSON of env vars including `PATH` modifications; parse and forward into sandbox ENV_ARGS. |
### Differentiators for v2.0
Features that go beyond minimum requirements and add meaningful value.
| Feature | Value Proposition | Complexity | Notes |
|---------|-------------------|------------|-------|
| **Profile inheritance / `extends`** | A "work" profile that extends "default" instead of rewriting it avoids duplication and drift | LOW | Simple key in profile config: `extends = "default"`. Load base profile first, merge overrides. Two levels sufficient; no deep inheritance chains needed. |
| **Network tier shown in env audit** | User sees "Network: internet-only" in pre-launch audit alongside env vars | LOW | Extend existing audit display to include active profile name and network tier. Auditability is the core UX principle of claudebox. |
| **Instance dir auto-cleanup** | Old instance dirs from deleted projects waste disk space over time | LOW | Add `claudebox --gc` that removes instance dirs for paths that no longer exist on disk. |
| **Profile `--list` and `--show`** | Discoverability of what profiles exist and what they contain | LOW | `claudebox --list-profiles` and `claudebox --show-profile foo`. Text output, no interactive UX needed. |
| **`nix print-dev-env` opt-in flag** | Devshell injection requires `nix eval` which can be slow; should be explicit opt-in not auto | LOW | `--devshell` flag or `devshell = true` in profile. Never run `nix print-dev-env` implicitly. |
### Anti-Features for v2.0
Features that seem natural extensions but should be explicitly avoided.
| Anti-Feature | Why Avoid | What to Do Instead |
|--------------|-----------|-------------------|
| **Domain-level network allowlists** | Already declared out of scope in PROJECT.md. slirp4netns can be combined with iptables for domain filtering, but complexity is extreme and maintenance burden is high | Three tiers (full/internet-only/none) cover the actual use cases |
| **Writable auth file mounts** | Mounting `.credentials.json` read-write means Claude could overwrite or corrupt tokens | Mount read-only; Claude Code reads tokens, does not need to write them during a session (token refresh path is rare and can be handled by relaunching) |
| **Auto-detect and inject all devShell vars** | Running `nix print-dev-env` silently on every launch for any project with a `flake.nix` breaks the "no surprises" principle and is slow | Explicit `--devshell` flag only |
| **Profile passwords / encryption** | Profiles contain env var names, mount paths, package names — not secret values. API keys still come from host env via allowlist | Keep profile files plaintext; they don't need to store secrets |
| **Per-profile CLAUDE.md injection** | Profiles should not override sandbox system prompt — that breaks the security invariant that Claude always knows it's sandboxed | SANDBOX.md injection is unconditional; profiles can only add mounts/env/packages/network |
| **Multi-profile activation** | "Merge profile A and profile B" creates order-dependent ambiguity | One active profile per launch. Use `extends` for composition. |
| **Automatic profile detection from project** | Detecting profile from `.claudebox.toml` in project dir requires trusting project-local config, which is a sandbox escape vector | Profiles live in `~/.claudebox/profiles/` only. User chooses profile at launch. |
---
## Feature Dependencies (v2.0)
```
Per-project instance isolation
└──requires──> Auth passthrough works correctly
(auth must be in instance dir or globally available)
Named profiles
└──provides──> Network tier configuration
└──provides──> Extra env vars (additive to base allowlist)
└──provides──> Extra mount paths
└──provides──> Nix package list for PATH injection
Tiered network isolation
└──requires──> "none" tier: just --unshare-net (trivial, no deps)
└──requires──> "internet-only" tier: slirp4netns binary + process management
└──requires──> slirp4netns in runtimeInputs
└──requires──> pid-file or fd-passing to connect slirp4netns to bwrap net ns
Nix devshell injection
└──requires──> `nix print-dev-env` available inside pre-launch environment
└──requires──> CWD has a flake.nix (validated before attempting)
└──enhances──> Named profiles (devshell can be a profile option)
Profile inheritance (extends)
└──requires──> Named profiles exist first
```
### Dependency Notes
- **Auth passthrough and instance isolation interact:** If each instance dir is fully self-contained, auth files must either be in the instance dir (copied/linked from `~/.claude`) or mounted globally. The cleanest approach: mount `~/.claude/.credentials.json` read-only directly into `~/.claudebox/.credentials.json` unconditionally, keep it separate from instance isolation (which only scopes project history and todos).
- **internet-only tier is a process management problem:** slirp4netns must be started before bwrap, connected to bwrap's network namespace via the `--netns-type=path` approach or by passing the network namespace fd. bwrap's `--userns-block-fd` / `--info-fd` / `--json-status-fd` flags provide synchronization primitives for this. This is the highest-complexity feature in the milestone.
- **Nix devshell injection does not require profile system:** It can be an independent `--devshell` flag. Profile system can optionally set `devshell = true`. The two features are parallel, not sequential.
---
## Implementation Complexity Ranking
Ordered from lowest to highest complexity, within the v2.0 scope:
| Feature | Complexity | Key Challenge |
|---------|------------|---------------|
| Auth passthrough | LOW | Identify correct files; decide ro vs rw; one-time mount addition |
| Network tier: none | LOW | Add `--unshare-net` to bwrap call when tier=none |
| Profile --list/--show | LOW | File glob + pretty-print |
| Network tier in audit display | LOW | Extend existing audit code |
| Per-project instance isolation | LOW-MEDIUM | Hash CWD, mkdir, adjust bind mount target |
| Named profiles (parsing + merge) | MEDIUM | Config file format, merge logic, validation |
| Nix devshell injection | MEDIUM | `nix print-dev-env` output parsing, env merge, PATH extension |
| Network tier: internet-only | HIGH | slirp4netns process lifecycle, bwrap netns synchronization, resolv.conf injection |
---
## Auth Passthrough: Key Findings
Claude Code on Linux stores auth at `~/.claude/.credentials.json` (mode 0600). This file contains OAuth tokens. It is the only file strictly required for subscription/API key auth to work. (Source: inventivehq.com Claude Code configuration guide, verified 2026-04-10.)
Mount strategy:
- `--ro-bind "$HOME/.claude/.credentials.json" "$HOME/.claudebox/.credentials.json"` — read-only passthrough
- If file does not exist (user not authenticated), skip the mount silently; Claude Code will prompt for auth at first launch and write to the instance dir
- `settings.json` and `settings.local.json` in `~/.claude/` contain user preferences — mounting these read-only is optional but reduces friction (user's saved settings carry over)
---
## Per-Project Instance Isolation: Key Findings
Claude Code stores per-project data at `~/.claude/projects/{encoded-path}/` where encoding replaces `/` and `.` with `-`. (Source: milvus.io Claude Code local storage deep-dive, verified 2026-04-10.) Per-project instance dirs in claudebox would contain their own `~/.claude/`-equivalent, keeping conversation history, todos, and shell snapshots scoped per project.
Instance dir path: `~/.claudebox/instances/$(echo -n "$CWD" | sha256sum | cut -c1-16)/`
The bind-mount that currently maps `~/.claudebox``~/.claude` inside the sandbox would instead map `~/.claudebox/instances/<hash>``~/.claude`. Auth files should NOT live inside instance dirs (they are cross-project); they should be mounted separately.
---
## Tiered Network Isolation: Key Findings
**Tier: none**`--unshare-net` added to bwrap flags. No other changes. Claude Code cannot reach any network. DNS fails. Nix store is local so tool installation via comma still works.
**Tier: internet-only** — Requires slirp4netns (in nixpkgs as `pkgs.slirp4netns`). The pattern:
1. bwrap is invoked with `--unshare-net` plus `--info-fd 4` to emit the sandbox PID
2. A wrapper script reads the PID from the info fd
3. slirp4netns is started: `slirp4netns --configure --disable-host-loopback <pid> tap0`
4. `--disable-host-loopback` prevents access to host's 127.x.x.x, 10.x.x.x (LAN), and Tailscale addresses
5. A custom `resolv.conf` pointing to slirp4netns's built-in DNS (10.0.2.3) is bind-mounted
Important: bwrap's `--info-fd` is a synchronization mechanism — bwrap blocks until the info fd is closed, allowing the wrapper to set up the network namespace before Claude Code starts. (Source: bwrap man page, slirp4netns GitHub README, verified 2026-04-10.)
slirp4netns does not require root privileges. It is in nixpkgs and works on NixOS.
**Tier: full** — Current behavior, no changes.
---
## Named Profiles: Design Recommendation
Config format: plain shell-style or TOML. Given the project is pure shell script with no TOML parser available in runtimeInputs, **shell key=value format** (like `.env` files) is simplest:
```sh
# ~/.claudebox/profiles/web.conf
network=internet-only
devshell=false
env=MY_EXTRA_VAR,ANOTHER_VAR
mount_ro=/home/user/shared-docs
packages=awscli2 kubectl
extends=default
```
Parsing: `source`-able if shell syntax, or simple `grep`/`awk` for key=value. Shell `source` is dangerous (arbitrary code execution) — use `awk -F= '{print $1, $2}'` parser instead.
---
## Nix Devshell Injection: Design Recommendation
`nix print-dev-env --json .#devShell` (or just `.` for default devShell) outputs a JSON object with all environment variables the devShell would set, including a modified `PATH`. Parsing this with `jq` (already in runtimeInputs) and merging into `ENV_ARGS` is the right approach.
Caveats:
- `nix print-dev-env` runs Nix evaluation — can be slow (2-10s) for complex flakes
- Must be run from CWD before entering sandbox
- Not all devShell vars are safe to forward (some reference host paths outside /nix/store)
- Should filter to only PATH modifications and explicitly safe vars
---
## Sources
- [Claude Code Authentication Docs](https://code.claude.com/docs/en/authentication) — auth file locations (HIGH confidence)
- [inventivehq.com Claude Code config guide](https://inventivehq.com/knowledge-base/claude/where-configuration-files-are-stored) — file list verification (MEDIUM confidence)
- [Claude Code .credentials.json bug issue #1414](https://github.com/anthropics/claude-code/issues/1414) — confirms Linux credential file path (HIGH confidence)
- [milvus.io Claude Code local storage](https://milvus.io/blog/why-claude-code-feels-so-stable-a-developers-deep-dive-into-its-local-storage-design.md) — project dir encoding, storage layout (MEDIUM confidence)
- [slirp4netns README](https://github.com/rootless-containers/slirp4netns) — network isolation mechanics, --disable-host-loopback (HIGH confidence)
- [slirp4netns man page](https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md) — --disable-host-loopback details (HIGH confidence)
- [bubblewrap issue #392](https://github.com/containers/bubblewrap/issues/392) — slirp4netns+bwrap integration status (not merged into bwrap; external process approach needed) (HIGH confidence)
- [bwrap man page](https://manpages.debian.org/unstable/bubblewrap/bwrap.1.en.html) — --info-fd synchronization mechanism (HIGH confidence)
- Training data: nix print-dev-env JSON output format, jq availability (MEDIUM confidence — verify exact flags)
---
*Feature research updated for v2.0 milestone: network isolation, profiles, auth passthrough, instance isolation, devshell injection*
*Researched: 2026-04-10*