claudebox/.planning/research/ARCHITECTURE.md

28 KiB
Raw Blame History

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/  │  │ <cwd-hash>/.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/<hash>)         │ │
│  │  - 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/<hash>     │
               │  ~/.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/<name>.json New function
claudebox.sh — instance resolver Hash CWD → ~/.claudebox/instances/<hash>/, 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/<name>.json New on-disk schema
Instance store ~/.claudebox/instances/<cwd-hash>/ New on-disk layout
Auth source ~/.claude/.credentials.json (host, read-only mount) New mount only
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/<hash>/: 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/<hash>/.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):

# 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:

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.

# 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_TOKENANTHROPIC_API_KEYapiKeyHelperCLAUDE_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:

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/<name>.json describing what a named profile adds to the baseline sandbox.

Schema:

{
  "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):

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.

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/<hash>/ ~/.claudebox/ rw (bind)
Per-project .claude ~/.claudebox/instances/<hash>/.claude/ ~/.claude/ (via symlink) rw
Host auth token ~/.claude/.credentials.json ~/.claude/.credentials.json ro
Profile definitions ~/.claudebox/profiles/<name>.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/<name>.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/<hash>. 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 1351, 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 evalpkgs.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