# Architecture Research **Domain:** bwrap sandbox wrapper — network isolation, profiles, auth passthrough **Researched:** 2026-04-10 **Confidence:** HIGH (existing codebase read directly; new feature patterns verified against nixpkgs, official Claude Code docs, and upstream slirp4netns/pasta documentation) ## Standard Architecture ### System Overview — v2.0 with new features ``` claudebox invocation | v ┌───────────────────────────────────────────────────────────────────┐ │ claudebox.sh (writeShellApplication, Nix store) │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────────┐ │ │ │ Arg parse │ │Profile load │ │ Instance dir resolution │ │ │ │ --profile │ │ ~/.claudebox│ │ ~/.claudebox/instances/ │ │ │ │ --network │ │ /profiles/ │ │ /.claude/ │ │ │ └──────┬──────┘ └──────┬──────┘ └────────────┬─────────────┘ │ │ └────────────────┴──────────────┬─────────┘ │ │ v │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Env builder │ │ │ │ - sandbox-generated vars (HOME, USER, PATH, SHELL ...) │ │ │ │ - host allowlist (TERM, LANG, ANTHROPIC_API_KEY ...) │ │ │ │ - profile env vars injected here │ │ │ │ - CLAUDEBOX_EXTRA_ENV escape hatch │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ | │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Mount builder │ │ │ │ - core mounts (nix store, /etc/*, /proc, /dev, tmpfs) │ │ │ │ - auth passthrough (ro-bind ~/.claude/.credentials.json) │ │ │ │ - instance dir (bind ~/.claudebox/instances/) │ │ │ │ - profile extra mounts appended here │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ | │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Network tier decision │ │ │ │ full → no --unshare-net, share host network │ │ │ │ inet → --unshare-net + pasta sidecar (internet, no LAN) │ │ │ │ none → --unshare-net, no sidecar (offline) │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ | │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ Pre-launch: env audit display + confirmation │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ | │ │ full/none: exec bwrap ... | inet: bwrap ... & │ │ | pasta $BWRAP_PID │ │ | wait $BWRAP_PID │ └───────────────────────────────────────────────────────────────────┘ | ┌──────────────────────────┴───────────────────────┐ │ bwrap sandbox │ │ ~/.claudebox → ~/.claudebox/instances/ │ │ ~/.claude symlink → ~/.claudebox │ │ ~/.claude/.credentials.json (read-only) │ │ CWD (read-write) │ │ profile devshell PATH prepended to SANDBOX_PATH │ │ network: full | inet (via pasta TAP) | none │ └──────────────────────────────────────────────────┘ ``` ### Component Responsibilities | Component | Responsibility | Status (v2.0) | |-----------|---------------|----------------| | `flake.nix` | Nix derivation, `runtimeInputs`, `SANDBOX_PATH` injection | Modified — add `pkgs.passt` | | `claudebox.sh` — arg parse | CLI flags | Modified — add `--profile`, `--network` | | `claudebox.sh` — profile loader | Read `~/.claudebox/profiles/.json` | New function | | `claudebox.sh` — instance resolver | Hash CWD → `~/.claudebox/instances//`, create if missing | New function | | `claudebox.sh` — env builder | Accumulate `--setenv` args | Modified — add profile env injection | | `claudebox.sh` — mount builder | Accumulate bwrap mount args | Modified — auth ro-bind, instance bind, profile mounts | | `claudebox.sh` — network setup | Decide `--unshare-net` + pasta sidecar | New function | | `claudebox.sh` — package injector | Resolve profile packages via `nix build`, prepend to `SANDBOX_PATH` | New function | | `claudebox.sh` — exec block | `exec bwrap` or `bwrap & wait` depending on tier | Modified — split by network tier | | Profile store | `~/.claudebox/profiles/.json` | New on-disk schema | | Instance store | `~/.claudebox/instances//` | New on-disk layout | | Auth source | `~/.claude/.credentials.json` (host, read-only mount) | New mount only | ## Recommended Project Structure ``` claudebox/ ├── flake.nix # add pkgs.passt to runtimeDeps ├── claudebox.sh # main script — extended with new sections └── profiles/ # optional bundled example profiles └── example.json ~/.claudebox/ # runtime state on host ├── CLAUDE.md # existing ├── SANDBOX.md # existing, overwritten each launch ├── profiles/ # user-defined profiles │ ├── default.json │ ├── work.json │ └── offline.json └── instances/ # per-project conversation history ├── a3f7b2c1d9e1f430/ # sha256 of /home/user/projects/foo (16 hex chars) │ └── .claude/ # conversations, settings scoped to this project │ ├── settings.local.json │ └── projects/ └── d9e1f430a3f7b2c1/ └── .claude/ ``` ### Structure Rationale - **`profiles/`:** Flat JSON keeps profiles shell-readable with `jq` without a Nix dependency at profile-load time. A `.nix` variant is possible for devshell injection but adds complexity; JSON is preferred for v2.0. - **`instances//`:** Hashing the absolute CWD path gives stable, collision-resistant names without encoding slashes. `sha256sum` truncated to 16 hex chars is sufficient — 2^64 space, no practical collision risk. - **`instances//.claude/`:** Claude Code reads `~/.claude/` for all state (conversation history in `projects/`, settings in `settings.local.json`). Mapping this per-project gives isolated history automatically without any changes to Claude Code itself. ## Architectural Patterns ### Pattern 1: Instance Dir via CWD Hash **What:** Derive a stable instance directory from `sha256sum` of the absolute CWD. Create on first use. Bind-mount it as `~/.claudebox` inside the sandbox (preserving the existing `~/.claudebox → ~/.claude` symlink that already exists in the script). **When to use:** Every launch. Replaces the current single `~/.claudebox` bind-mount target. **Trade-offs:** One `sha256sum` call and one `mkdir -p` per launch. Negligible latency. History is siloed per CWD, which is the desired behavior. **Integration point in existing code (line 345-346 of claudebox.sh):** ```bash # EXISTING (line ~345 in current claudebox.sh): # --bind "$HOME/.claudebox" "$HOME/.claudebox" \ # --symlink "$HOME/.claudebox" "$HOME/.claude" \ # REPLACE WITH: INSTANCE_HASH=$(printf '%s' "$CWD" | sha256sum | cut -c1-16) INSTANCE_DIR="$HOME/.claudebox/instances/$INSTANCE_HASH" mkdir -p "$INSTANCE_DIR/.claude" # In bwrap call: # --bind "$INSTANCE_DIR" "$HOME/.claudebox" \ # --symlink "$HOME/.claudebox" "$HOME/.claude" \ (unchanged) ``` The `~/.claudebox → ~/.claude` symlink line is unchanged. The SANDBOX.md write and CLAUDE.md prepend logic (lines 107-154) currently targets `$HOME/.claudebox/` — these should continue targeting `$HOME/.claudebox/` on the host (the shared root), not the instance dir. SANDBOX.md is global to all instances; CLAUDE.md is too. Only conversation history (inside `.claude/`) is per-instance. ### Pattern 2: Auth Passthrough as Read-Only Mount **What:** Mount `~/.claude/.credentials.json` from the host into the sandbox at the same path, read-only. No other files from the host `~/.claude/` are mounted. **When to use:** Always (unconditional in v2.0). **Why only `.credentials.json`:** Verified against official Claude Code docs — on Linux, credentials are stored at `~/.claude/.credentials.json` (mode 0600). The host `~/.claude/` otherwise contains all historical conversation state. We do not want that leaking into the sandbox; only the auth token is needed. **Path resolution inside sandbox:** The sandbox's `~/.claude/` is the instance dir (via `~/.claudebox → ~/.claude` symlink). The credential file must be bound inside this path: ```bash AUTH_CREDS="$HOME/.claude/.credentials.json" if [[ -f "$AUTH_CREDS" ]]; then # In bwrap mount args (after the instance dir bind and symlink): MOUNT_ARGS+=(--ro-bind "$AUTH_CREDS" "$HOME/.claudebox/.claude/.credentials.json") # Which resolves to ~/.claude/.credentials.json inside sandbox fi ``` Wait — the symlink chain inside sandbox is: `~/.claudebox` is the instance dir, `~/.claude` symlinks to `~/.claudebox`. So `~/.claude/.credentials.json` → `~/.claudebox/.credentials.json` → `$INSTANCE_DIR/.credentials.json`. The ro-bind should target `$HOME/.claudebox/.credentials.json` inside bwrap's view of the namespace (which is the instance dir bind target). Simpler alternative: bind directly to the resolved path `$INSTANCE_DIR/.credentials.json` on the host before launching bwrap. ```bash # Simpler: copy (or bind) creds into instance dir before launch # ro-bind from host ~/.claude/.credentials.json # to instance dir path that becomes ~/.claude/.credentials.json inside sandbox if [[ -f "$HOME/.claude/.credentials.json" ]]; then MOUNT_ARGS+=(--ro-bind "$HOME/.claude/.credentials.json" \ "$INSTANCE_DIR/.credentials.json") fi # Then inside sandbox: ~/.claude/.credentials.json exists read-only ``` **Credential precedence note (HIGH confidence — official docs):** Claude Code selects credentials in this order: cloud provider env vars → `ANTHROPIC_AUTH_TOKEN` → `ANTHROPIC_API_KEY` → `apiKeyHelper` → `CLAUDE_CODE_OAUTH_TOKEN` → OAuth file at `~/.claude/.credentials.json`. The existing host-allowlist for `ANTHROPIC_API_KEY` therefore continues to take precedence over the passthrough file if both are present. ### Pattern 3: Tiered Network via Sidecar Process **What:** Three network tiers controlled by `--network` flag (default: `full`): - `full` — no `--unshare-net`; sandbox shares host network. Current behavior. - `inet` — `--unshare-net` + pasta sidecar; internet access via userspace NAT, no LAN, no Tailscale, no host loopback services. - `none` — `--unshare-net` alone; loopback only, fully offline. **When to use:** Expose via `--network inet|none|full` CLI flag. Profile `network` field sets the default for that profile (overridable by CLI flag). **pasta vs slirp4netns decision — use pasta:** - Both `pkgs.passt` (version `2025_09_19.623dbf6`) and `pkgs.slirp4netns` (version `1.3.3`) are in nixpkgs and verified available. - pasta is the current default in Podman 5 and RHEL 9.5; actively maintained. - pasta avoids NAT (it forwards at Layer-4 via native sockets), giving better performance and simpler DNS. - pasta supports `--no-map-gw` to prevent host gateway access and `--no-tcp-ports --no-udp-ports` to block incoming connections. - slirp4netns has `--disable-host-loopback` but still routes to LAN by default. - **Add `pkgs.passt` to `runtimeDeps` in `flake.nix`.** Keep `slirp4netns` as a named alternative but do not add it by default. **Mechanism for `inet` tier — critical exec change:** The current script ends with `exec bwrap ...` (line 327, claudebox.sh). `exec` replaces the shell process, leaving no parent to launch a sidecar. For `inet` mode, the script must fork instead: ```bash if [[ "$NETWORK_TIER" == "inet" ]]; then BWRAP_PIDFILE=$(mktemp) trap 'rm -f "$BWRAP_PIDFILE"' EXIT bwrap \ --unshare-net \ --pidfile "$BWRAP_PIDFILE" \ "${ENV_ARGS[@]}" \ ... (all other mount args) ... \ -- "${SANDBOX_CMD[@]}" & BWRAP_PID=$! # Wait for bwrap to write its PID (namespace is ready when pidfile is non-empty) until [[ -s "$BWRAP_PIDFILE" ]]; do sleep 0.05; done SANDBOX_PID=$(cat "$BWRAP_PIDFILE") # Attach pasta to the network namespace pasta --config-net --no-tcp-ports --no-udp-ports "$SANDBOX_PID" & PASTA_PID=$! wait "$BWRAP_PID" STATUS=$? kill "$PASTA_PID" 2>/dev/null || true exit "$STATUS" elif [[ "$NETWORK_TIER" == "none" ]]; then exec bwrap --unshare-net "${ENV_ARGS[@]}" ... -- "${SANDBOX_CMD[@]}" else # full (default) exec bwrap "${ENV_ARGS[@]}" ... -- "${SANDBOX_CMD[@]}" fi ``` **DNS for `inet` mode:** pasta provides DNS forwarding automatically through its virtual gateway. The existing `/etc/resolv.conf` ro-bind must be REMOVED for `inet` mode (the sandbox resolv.conf would point to host DNS on the host network, unreachable in the new namespace). pasta configures the TAP device with `10.0.2.100` and routes DNS queries through its gateway. A synthetic `/etc/resolv.conf` inside the sandbox pointing to `10.0.2.3` (pasta's default gateway DNS) should be provided via `--ro-bind` from a tempfile. **`--pidfile` flag availability:** bwrap has supported `--pidfile` since version 0.4.0. The nixpkgs version is 0.9.x, so this is available. (HIGH confidence — verified against bwrap manpage.) ### Pattern 4: Profile Schema (JSON) **What:** A flat JSON file at `~/.claudebox/profiles/.json` describing what a named profile adds to the baseline sandbox. **Schema:** ```json { "name": "work", "network": "inet", "env": { "AWS_PROFILE": "work" }, "extra_env_passthrough": ["MY_ORG_TOKEN", "VAULT_TOKEN"], "mounts": [ { "host": "~/.aws/credentials", "sandbox": "~/.aws/credentials", "mode": "ro" } ], "packages": ["awscli2", "kubectl"] } ``` **Loading with `jq` (already in `runtimeDeps`):** ```bash PROFILE_NAME="${CLAUDEBOX_PROFILE:-${1#--profile=}}" # or parsed via arg loop PROFILE_FILE="$HOME/.claudebox/profiles/${PROFILE_NAME}.json" if [[ -f "$PROFILE_FILE" ]]; then PROFILE_NETWORK=$(jq -r '.network // empty' "$PROFILE_FILE") mapfile -t PROFILE_PACKAGES < <(jq -r '.packages[]? // empty' "$PROFILE_FILE") mapfile -t PROFILE_PASSTHROUGH < <(jq -r '.extra_env_passthrough[]? // empty' "$PROFILE_FILE") # ... etc fi ``` **Profile env injection:** Profile `env` entries are treated like `CLAUDEBOX_EXTRA_ENV` values — they are sandbox-side constants (not host passthrough). The `extra_env_passthrough` list extends `HOST_ALLOWLIST` dynamically, allowing host env vars not in the hardcoded allowlist to pass through when a profile explicitly permits them. This preserves the allowlist security model: the profile, not the shell environment, decides what's allowed. ### Pattern 5: Nix Package Injection **What:** Profile `packages` field lists nixpkgs attribute names. Resolved via `nix build --no-link --print-out-paths` before launch. Results prepended to `SANDBOX_PATH`. **Why `nix build` not `nix shell`:** `nix shell nixpkgs#pkg` spawns a child shell and cannot inject into the *parent's* `SANDBOX_PATH` variable. `nix build --print-out-paths` returns the store path, which can be prepended to the PATH string before it's passed to `--setenv`. ```bash EXTRA_BIN_PATHS="" for pkg in "${PROFILE_PACKAGES[@]}"; do pkg_out=$(nix build --no-link --print-out-paths "nixpkgs#${pkg}" 2>/dev/null) || { echo "Warning: could not resolve profile package: $pkg" >&2 continue } EXTRA_BIN_PATHS="${pkg_out}/bin:${EXTRA_BIN_PATHS}" done SANDBOX_PATH="${EXTRA_BIN_PATHS}${SANDBOX_PATH}" ``` **Latency:** Zero if the store path is already built/cached. First-time use triggers a download; thereafter cached. The existing `nix` daemon socket bind ensures builds work inside the script (the script itself runs with full host access, not inside bwrap). **devshell injection (deferred):** Full `nix develop .#devShell` integration — where a project's own `flake.nix` devShell is evaluated and its environment injected — is significantly more complex (requires evaluating the flake, capturing `buildEnv`, etc.). Defer to a later milestone. The `packages` field covers the practical 80% use case. ## Data Flow ### Launch Flow (inet tier, with profile) ``` claudebox --profile work --network inet | [Arg parse] → PROFILE_NAME=work, NETWORK_TIER=inet | [Profile load] ~/.claudebox/profiles/work.json → PROFILE_NETWORK=inet, PROFILE_PACKAGES=[awscli2], PROFILE_ENV={AWS_PROFILE=work}, PROFILE_MOUNTS=[~/.aws/credentials] | [Instance resolve] INSTANCE_HASH = sha256("$CWD")[0:16] mkdir -p ~/.claudebox/instances/$INSTANCE_HASH/.claude | [Package resolve] nix build nixpkgs#awscli2 → /nix/store/xxx-awscli2 SANDBOX_PATH = /nix/store/xxx-awscli2/bin:$SANDBOX_PATH | [Env build] base vars (HOME, USER, PATH=SANDBOX_PATH, SHELL, ...) + profile env (AWS_PROFILE=work) + host allowlist (TERM, LANG, ANTHROPIC_API_KEY if set, ...) + profile extra_env_passthrough (MY_ORG_TOKEN if set on host) | [Mount build] core mounts (nix store, /etc/*, proc, dev, tmpfs ...) + instance dir bind (→ ~/.claudebox inside sandbox) + ~/.claudebox → ~/.claude symlink (unchanged) + auth creds ro-bind (~/.claude/.credentials.json) + profile mounts (~/.aws/credentials ro) | [DNS tempfile for inet mode] echo "nameserver 10.0.2.3" > /tmp/resolv-$$.conf (replaces the /etc/resolv.conf ro-bind) | [Env audit display + confirmation] | [Network: inet] bwrap --unshare-net --pidfile $PIDFILE ... & BWRAP_PID=$! until [[ -s $PIDFILE ]]; do sleep 0.05; done pasta --config-net --no-tcp-ports --no-udp-ports $(cat $PIDFILE) & wait $BWRAP_PID → exit with its status | [Inside sandbox] ~/.claude/ = instance dir for this CWD (isolated history) ~/.claude/.credentials.json (ro, host OAuth token) awscli2 in PATH AWS_PROFILE=work in env ~/.aws/credentials accessible (ro) internet via pasta, no LAN, no Tailscale, no host loopback ``` ### State Locations (host) | Purpose | Host Path | Sandbox Path | Mode | |---------|-----------|-------------|------| | Shared claudebox config | `~/.claudebox/` | n/a (host-side only) | rw | | SANDBOX.md / CLAUDE.md | `~/.claudebox/SANDBOX.md` | n/a | rw | | Per-project history | `~/.claudebox/instances//` | `~/.claudebox/` | rw (bind) | | Per-project .claude | `~/.claudebox/instances//.claude/` | `~/.claude/` (via symlink) | rw | | Host auth token | `~/.claude/.credentials.json` | `~/.claude/.credentials.json` | ro | | Profile definitions | `~/.claudebox/profiles/.json` | not mounted | — | ## Anti-Patterns ### Anti-Pattern 1: Mounting entire `~/.claude` for auth passthrough **What people do:** `--bind ~/.claude ~/.claude` to get credentials working. **Why it's wrong:** Exposes all conversation history, settings, and any file Claude Code stores in `~/.claude`. Also breaks per-project isolation because the host `.claude` overwrites the instance dir. **Do this instead:** Mount only `~/.claude/.credentials.json` read-only. Keep the instance dir as the effective `~/.claude` target inside the sandbox. ### Anti-Pattern 2: `exec bwrap` for all network tiers **What people do:** Keep `exec bwrap` unconditionally for simplicity even when adding `inet` mode. **Why it's wrong:** `exec` replaces the shell process. There is no parent process left to launch the pasta sidecar after the bwrap namespace is created. **Do this instead:** Branch on `NETWORK_TIER`: use `exec bwrap` for `full` and `none` (no sidecar needed); use `bwrap ... &` + `wait` for `inet`. ### Anti-Pattern 3: Hardcoding secrets in profile JSON **What people do:** `"env": { "AWS_SECRET_ACCESS_KEY": "hardcoded-value" }` in a profile file. **Why it's wrong:** Profile files at `~/.claudebox/profiles/` are plaintext. Hardcoded secrets become plaintext files on disk. **Do this instead:** Use `extra_env_passthrough` to declare which host env vars are allowed into the sandbox. The value must be set in the host shell environment before running claudebox. The profile says "permit this variable" not "store this value." ### Anti-Pattern 4: Per-profile `nix develop` evaluation at launch **What people do:** `nix develop .#profileEnv` to inject a full devshell environment at launch time. **Why it's wrong:** `nix develop` evaluates a flake, which takes hundreds of milliseconds to seconds even with caching. It also requires the profile to point to a valid flake with a devShell output. **Do this instead:** Profile `packages` field lists nixpkgs attribute names. Resolve via `nix build --no-link --print-out-paths`. Fast (cached after first use), no flake required. Full devshell support is a later milestone. ### Anti-Pattern 5: Removing `/etc/resolv.conf` mount for all tiers when adding inet support **What people do:** Remove the resolv.conf bind globally when adding network isolation, breaking `full` mode DNS. **Why it's wrong:** `full` and `none` tiers still rely on the `/etc/resolv.conf` bind-mount. Only `inet` mode needs it replaced with a pasta-managed DNS address. **Do this instead:** Branch the resolv.conf mount on `NETWORK_TIER`: `full`/`none` keep the ro-bind from host; `inet` provides a tempfile pointing to pasta's gateway DNS (`nameserver 10.0.2.3`). ## Integration Points ### New vs Modified Components | Component | Change Type | What Changes | |-----------|------------|--------------| | `flake.nix` | Modified | Add `pkgs.passt` to `runtimeDeps` (one line) | | `claudebox.sh` — arg parse | Modified | Add `--profile NAME`, `--network full\|inet\|none` flags | | `claudebox.sh` — exec block | Modified | Split `exec bwrap` into three branches by `NETWORK_TIER` | | `claudebox.sh` — mount builder | Modified | Replace fixed `~/.claudebox` bind with instance dir bind; add auth creds ro-bind; add profile mounts | | `claudebox.sh` — env builder | Modified | Add profile env injection after `CLAUDEBOX_EXTRA_ENV` block | | `claudebox.sh` — profile loader | New function | `jq` parse of `~/.claudebox/profiles/.json` | | `claudebox.sh` — instance resolver | New function | `sha256sum` of CWD, `mkdir -p`, set `INSTANCE_DIR` | | `claudebox.sh` — network setup | New function | Set `NETWORK_TIER` var; build `--unshare-net` flag; build pasta invocation for `inet` | | `claudebox.sh` — package injector | New function | `nix build` loop, build `EXTRA_BIN_PATHS` | | `~/.claudebox/profiles/` | New on-disk | User-created JSON profile files | | `~/.claudebox/instances/` | New on-disk | Auto-created per-project dirs | ### Build Order (dependency-ordered) 1. **Auth passthrough** — Smallest change: add one conditional ro-bind for `~/.claude/.credentials.json`. Validates that the instance dir and auth file coexist correctly before instance dir is fully wired up. No new Nix deps. Unblocks all downstream features that require Claude to authenticate inside the sandbox. 2. **Per-project instance dirs** — Replace the single `~/.claudebox` bind-mount target with `~/.claudebox/instances/`. Depends on auth passthrough (step 1) having the credential bind path logic stable. No new Nix deps. After this, each project has isolated conversation history. 3. **Tiered network — `none` tier** — Add `--network` flag and implement `none` with `--unshare-net` alone. No new deps; bwrap is already present. Validates the exec→wait refactor structural change independently of pasta. Low risk: if offline mode is broken, `full` mode is unaffected. 4. **Tiered network — `inet` tier (pasta)** — Add `pkgs.passt` to flake, implement pasta sidecar for `inet`. Depends on step 3 having the exec→wait refactor validated. DNS tempfile handling belongs here. This is the highest-risk step (new external process, timing dependency on `--pidfile`). 5. **Named profiles (env + mounts + network tier)** — Add `--profile` flag, implement JSON profile loader with `jq`, inject env/mounts/network-tier from profile. Depends on steps 2 and 4 being stable (profiles can control all three axes). Pure shell logic, no new deps. 6. **Nix package injection** — Add `packages` field to profile schema, implement `nix build` loop. Last because it has latency risk and is independently testable without touching any other subsystem. ## Sources - Existing codebase (`claudebox.sh` lines 1–351, `flake.nix`): direct read — HIGH confidence - Claude Code credential storage path on Linux (`~/.claude/.credentials.json`): official docs at `https://code.claude.com/docs/en/authentication` — HIGH confidence - Claude Code credential precedence order: same official docs — HIGH confidence - pasta/passt PID-based namespace attachment and `--config-net` flag: `https://passt.top/passt/about/` — MEDIUM confidence (pasta standalone bwrap integration not documented with exact flag combinations; verify `--no-tcp-ports` and DNS gateway address during implementation) - slirp4netns `--configure --disable-host-loopback` pattern: `https://github.com/rootless-containers/slirp4netns` manpage — MEDIUM confidence - nixpkgs package availability: verified locally via `nix eval` — `pkgs.passt` version `2025_09_19.623dbf6`, `pkgs.slirp4netns` version `1.3.3` — HIGH confidence - bwrap `--pidfile` flag: bwrap 0.4.0+ manpage; current nixpkgs has 0.9.x — HIGH confidence - pasta as Podman 5 / RHEL 9.5 default: WebSearch corroborated — MEDIUM confidence --- *Architecture research for: claudebox v2.0 — network isolation, profiles, auth passthrough* *Researched: 2026-04-10*