# 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//` 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/.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/` → `~/.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 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*