Claude Skill

sandbox-claude-inside

Run Claude Code INSIDE a ca-sandbox box (`--with-claude`). Routed to when the user wants an agent loop running against an isolated, ephemeral sandbox rather than the host. Authenticates via an env-injected CLAUDE_CODE_OAUTH_TOKEN with no host bind of ~/.claude; the image pins the

LLM Mart · 0 points · 18 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download arbiterforge-codearbiter-plugins_ca-sandbox_skills_sandbox-claude-inside-44989c5.zip · 4 KB
Part of arbiterforge/codearbiter — 238 skills

Install

skills CLI npx skills add https://github.com/arbiterForge/codeArbiter/tree/main/plugins/ca-sandbox/skills/sandbox-claude-inside
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install arbiterforge-codearbiter@llmmart
Git git clone https://github.com/arbiterForge/codeArbiter.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole arbiterforge/codearbiter collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

sandbox-claude-inside

Put Claude Code in the box, not the box on your machine. --with-claude runs the CLI inside a host-FS-isolated ca-sandbox container, authenticating from an env-injected token with no host bind of ~/.claude — the mechanism proven by Spike B (.codearbiter/spikes/ca-sandbox-claude-auth.md, CONFIRM-07). It is the deliberately-hardened lane: a token in a box is stealable, so the posture is locked down by construction (offline or Anthropic-only egress, token volume never shared with untrusted code), never left to operator discipline.

Pre-flight

Read these, or STOP and surface the gap — never guess the token source, the egress posture, or the persistence mechanism:

  • ${CLAUDE_PROJECT_DIR}/.codearbiter/spikes/ca-sandbox-claude-auth.md — the proven auth path (env token → real 401 on a dummy), the named-volume HOME persistence mechanism, and the load-bearing caveat that fixes the hard default.
  • ${CLAUDE_PROJECT_DIR}/.codearbiter/spikes/ca-sandbox-egress.md — why the egress allowlist is EXPERIMENTAL (CDN drift + DNS-exfil hole), so offline is the only GUARANTEED posture for a token-bearing box.
  • ${CLAUDE_PROJECT_DIR}/.codearbiter/decisions/0007-second-plugin-ca-sandbox.md — the governing decision; ca-sandbox is infrastructure, sibling to ca.

The shipped driver is plugins/ca-sandbox/tools/claude-inside.js, its OWN binary rather than a sandbox subcommand (#377):

node <plugin>/tools/claude-inside.js --image <tag> --home-volume <name> [--net offline|anthropic-only]

That separation IS the gate. A sandbox with-claude subcommand would let anyone start a token-bearing box with one ungated command, which would turn the five BLOCK phases below from enforcement into advice. This routine is the only sanctioned caller.

The token MUST come from the approved store as CLAUDE_CODE_OAUTH_TOKEN, in the ENVIRONMENT. The entry point refuses a --token flag outright: an argument list is world-readable, so passing a credential there publishes it to every process on the host. In tests use a DUMMY token only.

Sources: plugins/ca-sandbox/tools/claude-inside.ts (buildClaudeImageDockerfile, buildClaudeRunArgs, runClaudeInside, TokenCoMountRejectedError) and claude-inside-cli.ts (the entry).

Phase 1 — Posture · gate: BLOCK

Fix the egress posture and the trust boundary BEFORE anything is built or started. A token-bearing box is the one place ca-sandbox's FS-isolation invariant and a live credential are in direct tension — resolve it here, explicitly.

  • Egress — choose exactly one: offline (default, GUARANTEED — no interface at all) or anthropic-only (the EXPERIMENTAL Anthropic-domains allowlist, for interactive inference). No third option exists; a wide-open policy is rejected.
  • Trust boundary — the box runs Claude, NOT the untrusted source repo. Confirm the run mounts only the token/home volume, never the source volume at /work/repo. If the user wants Claude to read an untrusted repo, that is a SEPARATE, source-only box without the token — say so.

Gate: a named egress posture (offline or anthropic-only) AND an explicit statement that this box carries the token and NOT untrusted source. If the user asks for both at once, STOP and split them — the co-mount is forbidden (Phase 4).

Phase 2 — Image · gate: BLOCK

Build (or reuse) the pinned --with-claude image via buildClaudeImageDockerfile.

  • The image installs @anthropic-ai/claude-code@<pinned> — a PINNED semver, never @latest — and bakes DISABLE_AUTOUPDATER=1 so the box never silently pulls an unreviewed CLI into a token-bearing environment.
  • The base is node:22-slim (Spike B installed the CLI cleanly there), bound to a reviewed content DIGEST — node:22-slim@sha256:…, the CLAUDE_BASE_IMAGE constant. The tag is kept only as human-readable provenance; docker resolves the digest. This is the driver's highest-stakes pin: the base image's code runs in the SAME container as CLAUDE_CODE_OAUTH_TOKEN, so a retag or registry compromise would otherwise execute unreviewed code alongside a live credential. Pinning the CLI version alone is not enough. HOME is baked to the in-container claude home so the named volume has a writable mount point.

Gate: the image carries the exact pinned version (claude --version reports it), DISABLE_AUTOUPDATER=1, and a digest-pinned base. A floating or unpinned CLI — or an unpinned base image — fails the gate; image reproducibility is non-negotiable for a token box. Changing the digest is a reviewed dependency change: re-resolve it with docker buildx imagetools inspect, then re-run the credential-boundary and isolation suites.

Phase 3 — Token · gate: BLOCK

Source the OAuth token and confirm it is injected as ENV, never bound from the host.

  • The token comes from the approved secret store as CLAUDE_CODE_OAUTH_TOKEN (auth-precedence #5, from claude setup-token). It is env-injected (-e CLAUDE_CODE_OAUTH_TOKEN=…) — this IS the auth path; no host bind of ~/.claude is required or permitted.
  • The token MUST NOT be echoed to logs, written to a file the source volume can read, or passed into any LLM prompt. Prefer a scoped/short-lived setup-token.
  • Persistence: HOME is backed by a docker NAMED VOLUME, so the credential store $HOME/.claude/.credentials.json survives a restart on the volume — not on the host. A fresh container on the same volume resumes the session.

Gate: the token is from the approved store, env-injected (not bound), and never logged/persisted to a host-readable location. The home volume is a NAMED VOLUME, not a bind.

Phase 4 — Run · gate: BLOCK

Start the box via buildClaudeRunArgs / runClaudeInside. The builder enforces the guarantees by construction — do not hand-roll a docker run.

  • Mounts go through the one chokepoint (mounts.ts): the home named volume at HOME and a tmpfs /tmp. NO bind mount, NO /var/run/docker.sock, NEVER --privileged. Read-only root, non-root, no-new-privileges, resource caps — the same structural lockdown as any sandbox.
  • The egress posture from Phase 1 is applied: offline → --network none; anthropic-only → the experimental Anthropic-domains allowlist (custom bridge + NET_ADMIN/NET_RAW + the init-firewall script applied inside the box).
  • The CO-MOUNT GUARD: supplying a sourceVolume throws TokenCoMountRejectedError. The token volume is NEVER co-mounted with an untrusted-code run. This is the load-bearing Spike B caveat made structural — it is not optional.

Gate: the run argv was produced by the builder (not hand-rolled), the co-mount guard was not bypassed, the posture matches Phase 1, and a dummy token reaches AUTH (a real 401 Invalid bearer token) — proving the env token is the auth path before any real credential is used.

Phase 5 — Teardown · gate: BLOCK

Tear down per the lifecycle rules, deciding the fate of the credential volume.

  • Remove the container (docker rm -f). By default REMOVE the home/token volume too — a persisted credential store is a standing exfil target; keep it only on an explicit, recorded --keep-volume decision.
  • Every object created carries the ca.sandbox=1 label (plus a build marker in tests); the lifecycle/registry surfaces (destroy, prune) reclaim them.
  • Confirm zero leaked labeled containers/volumes after teardown (cached images excepted).

Gate: container removed; the credential volume removed unless --keep-volume was explicitly chosen and recorded; no leaked ca.sandbox=1 objects remain.

Hard rules

  • MUST authenticate via an env-injected CLAUDE_CODE_OAUTH_TOKEN — NEVER a host bind of ~/.claude.
  • MUST install a PINNED @anthropic-ai/claude-code@<semver> with DISABLE_AUTOUPDATER=1; MUST NOT use @latest or an unpinned CLI.
  • MUST default --with-claude egress to offline or anthropic-only; MUST NOT give a token-bearing box wide-open egress.
  • MUST NEVER co-mount the token/credential volume with an untrusted-code run (a run that mounts the source volume at /work/repo) — buildClaudeRunArgs throws TokenCoMountRejectedError and that throw MUST NOT be bypassed.
  • MUST back HOME with a docker NAMED VOLUME (never a bind) so the .claude credential store persists across restart on the volume, not on the host.
  • MUST NOT give the box a host bind mount, the docker socket, or --privileged; read-only root, non-root, and cap-drop hold as for any sandbox.
  • MUST source the token from the approved store; MUST NOT log it, write it to a host-readable file, or pass it into any LLM prompt. Use a DUMMY token in tests.
  • MUST remove the credential volume on teardown unless --keep-volume is an explicit, recorded decision.
  • MUST treat the egress allowlist as EXPERIMENTAL (Spike C): for a token-bearing box, offline is the only GUARANTEED posture.
Files (codearbiter)
  • SKILL.md 9.4 KB
    ---
    name: sandbox-claude-inside
    description: Run Claude Code INSIDE a ca-sandbox box (`--with-claude`). Routed to when the user wants an agent loop running against an isolated, ephemeral sandbox rather than the host. Authenticates via an env-injected CLAUDE_CODE_OAUTH_TOKEN with no host bind of ~/.claude; the image pins the CLI and disables the autoupdater; HOME is backed by a named volume so the .claude state persists across restart. Five gated phases — posture, image, token, run, teardown. The hard default is offline or Anthropic-domains-only egress, and the token volume is NEVER co-mounted with an untrusted-code run; both are enforced, not advised.
    ---
    
    # sandbox-claude-inside
    
    Put Claude Code in the box, not the box on your machine. `--with-claude` runs the
    CLI inside a host-FS-isolated ca-sandbox container, authenticating from an
    env-injected token with no host bind of `~/.claude` — the mechanism proven by
    Spike B (`.codearbiter/spikes/ca-sandbox-claude-auth.md`, CONFIRM-07). It is the
    deliberately-hardened lane: a token in a box is stealable, so the posture is locked
    down by construction (offline or Anthropic-only egress, token volume never shared
    with untrusted code), never left to operator discipline.
    
    ## Pre-flight
    
    Read these, or STOP and surface the gap — never guess the token source, the egress
    posture, or the persistence mechanism:
    
    - `${CLAUDE_PROJECT_DIR}/.codearbiter/spikes/ca-sandbox-claude-auth.md` — the
      proven auth path (env token → real `401` on a dummy), the named-volume HOME
      persistence mechanism, and the load-bearing caveat that fixes the hard default.
    - `${CLAUDE_PROJECT_DIR}/.codearbiter/spikes/ca-sandbox-egress.md` — why the
      egress allowlist is EXPERIMENTAL (CDN drift + DNS-exfil hole), so `offline` is
      the only GUARANTEED posture for a token-bearing box.
    - `${CLAUDE_PROJECT_DIR}/.codearbiter/decisions/0007-second-plugin-ca-sandbox.md`
      — the governing decision; ca-sandbox is infrastructure, sibling to `ca`.
    
    The shipped driver is `plugins/ca-sandbox/tools/claude-inside.js`, its OWN
    binary rather than a `sandbox` subcommand (#377):
    
    ```
    node <plugin>/tools/claude-inside.js --image <tag> --home-volume <name> [--net offline|anthropic-only]
    ```
    
    That separation IS the gate. A `sandbox with-claude` subcommand would let anyone
    start a token-bearing box with one ungated command, which would turn the five
    BLOCK phases below from enforcement into advice. This routine is the only
    sanctioned caller.
    
    The token MUST come from the approved store as `CLAUDE_CODE_OAUTH_TOKEN`, in the
    ENVIRONMENT. The entry point refuses a `--token` flag outright: an argument list
    is world-readable, so passing a credential there publishes it to every process
    on the host. In tests use a DUMMY token only.
    
    Sources: `plugins/ca-sandbox/tools/claude-inside.ts`
    (`buildClaudeImageDockerfile`, `buildClaudeRunArgs`, `runClaudeInside`,
    `TokenCoMountRejectedError`) and `claude-inside-cli.ts` (the entry).
    
    ## Phase 1 — Posture · gate: BLOCK
    
    Fix the egress posture and the trust boundary BEFORE anything is built or started.
    A token-bearing box is the one place ca-sandbox's FS-isolation invariant and a live
    credential are in direct tension — resolve it here, explicitly.
    
    - **Egress** — choose exactly one: `offline` (default, GUARANTEED — no interface at
      all) or `anthropic-only` (the EXPERIMENTAL Anthropic-domains allowlist, for
      interactive inference). No third option exists; a wide-open policy is rejected.
    - **Trust boundary** — the box runs Claude, NOT the untrusted source repo. Confirm
      the run mounts only the token/home volume, never the source volume at
      `/work/repo`. If the user wants Claude to read an untrusted repo, that is a
      SEPARATE, source-only box without the token — say so.
    
    Gate: a named egress posture (`offline` or `anthropic-only`) AND an explicit
    statement that this box carries the token and NOT untrusted source. If the user
    asks for both at once, STOP and split them — the co-mount is forbidden (Phase 4).
    
    ## Phase 2 — Image · gate: BLOCK
    
    Build (or reuse) the pinned `--with-claude` image via `buildClaudeImageDockerfile`.
    
    - The image installs `@anthropic-ai/claude-code@<pinned>` — a PINNED semver,
      never `@latest` — and bakes `DISABLE_AUTOUPDATER=1` so the box never silently
      pulls an unreviewed CLI into a token-bearing environment.
    - The base is `node:22-slim` (Spike B installed the CLI cleanly there), bound to a
      reviewed content DIGEST — `node:22-slim@sha256:…`, the `CLAUDE_BASE_IMAGE`
      constant. The tag is kept only as human-readable provenance; docker resolves the
      digest. This is the driver's highest-stakes pin: the base image's code runs in
      the SAME container as `CLAUDE_CODE_OAUTH_TOKEN`, so a retag or registry
      compromise would otherwise execute unreviewed code alongside a live credential.
      Pinning the CLI version alone is not enough. HOME is baked to the in-container
      claude home so the named volume has a writable mount point.
    
    Gate: the image carries the exact pinned version (`claude --version` reports it),
    `DISABLE_AUTOUPDATER=1`, and a digest-pinned base. A floating or unpinned CLI —
    or an unpinned base image — fails the gate; image reproducibility is
    non-negotiable for a token box. Changing the digest is a reviewed dependency
    change: re-resolve it with `docker buildx imagetools inspect`, then re-run the
    credential-boundary and isolation suites.
    
    ## Phase 3 — Token · gate: BLOCK
    
    Source the OAuth token and confirm it is injected as ENV, never bound from the host.
    
    - The token comes from the approved secret store as `CLAUDE_CODE_OAUTH_TOKEN`
      (auth-precedence #5, from `claude setup-token`). It is env-injected
      (`-e CLAUDE_CODE_OAUTH_TOKEN=…`) — this IS the auth path; no host bind of
      `~/.claude` is required or permitted.
    - The token MUST NOT be echoed to logs, written to a file the source volume can
      read, or passed into any LLM prompt. Prefer a scoped/short-lived setup-token.
    - Persistence: HOME is backed by a docker NAMED VOLUME, so the credential store
      `$HOME/.claude/.credentials.json` survives a restart on the volume — not on the
      host. A fresh container on the same volume resumes the session.
    
    Gate: the token is from the approved store, env-injected (not bound), and never
    logged/persisted to a host-readable location. The home volume is a NAMED VOLUME,
    not a bind.
    
    ## Phase 4 — Run · gate: BLOCK
    
    Start the box via `buildClaudeRunArgs` / `runClaudeInside`. The builder enforces the
    guarantees by construction — do not hand-roll a `docker run`.
    
    - Mounts go through the one chokepoint (`mounts.ts`): the home named volume at HOME
      and a tmpfs `/tmp`. NO bind mount, NO `/var/run/docker.sock`, NEVER
      `--privileged`. Read-only root, non-root, `no-new-privileges`, resource caps —
      the same structural lockdown as any sandbox.
    - The egress posture from Phase 1 is applied: `offline` → `--network none`;
      `anthropic-only` → the experimental Anthropic-domains allowlist (custom bridge +
      `NET_ADMIN`/`NET_RAW` + the init-firewall script applied inside the box).
    - The CO-MOUNT GUARD: supplying a `sourceVolume` throws `TokenCoMountRejectedError`.
      The token volume is NEVER co-mounted with an untrusted-code run. This is the
      load-bearing Spike B caveat made structural — it is not optional.
    
    Gate: the run argv was produced by the builder (not hand-rolled), the co-mount
    guard was not bypassed, the posture matches Phase 1, and a dummy token reaches AUTH
    (a real `401 Invalid bearer token`) — proving the env token is the auth path before
    any real credential is used.
    
    ## Phase 5 — Teardown · gate: BLOCK
    
    Tear down per the lifecycle rules, deciding the fate of the credential volume.
    
    - Remove the container (`docker rm -f`). By default REMOVE the home/token volume
      too — a persisted credential store is a standing exfil target; keep it only on an
      explicit, recorded `--keep-volume` decision.
    - Every object created carries the `ca.sandbox=1` label (plus a build marker in
      tests); the lifecycle/registry surfaces (`destroy`, `prune`) reclaim them.
    - Confirm zero leaked labeled containers/volumes after teardown (cached images
      excepted).
    
    Gate: container removed; the credential volume removed unless `--keep-volume` was
    explicitly chosen and recorded; no leaked `ca.sandbox=1` objects remain.
    
    ## Hard rules
    
    - MUST authenticate via an env-injected `CLAUDE_CODE_OAUTH_TOKEN` — NEVER a host
      bind of `~/.claude`.
    - MUST install a PINNED `@anthropic-ai/claude-code@<semver>` with
      `DISABLE_AUTOUPDATER=1`; MUST NOT use `@latest` or an unpinned CLI.
    - MUST default `--with-claude` egress to `offline` or `anthropic-only`; MUST NOT
      give a token-bearing box wide-open egress.
    - MUST NEVER co-mount the token/credential volume with an untrusted-code run (a run
      that mounts the source volume at `/work/repo`) — `buildClaudeRunArgs` throws
      `TokenCoMountRejectedError` and that throw MUST NOT be bypassed.
    - MUST back HOME with a docker NAMED VOLUME (never a bind) so the `.claude`
      credential store persists across restart on the volume, not on the host.
    - MUST NOT give the box a host bind mount, the docker socket, or `--privileged`;
      read-only root, non-root, and cap-drop hold as for any sandbox.
    - MUST source the token from the approved store; MUST NOT log it, write it to a
      host-readable file, or pass it into any LLM prompt. Use a DUMMY token in tests.
    - MUST remove the credential volume on teardown unless `--keep-volume` is an
      explicit, recorded decision.
    - MUST treat the egress allowlist as EXPERIMENTAL (Spike C): for a token-bearing
      box, `offline` is the only GUARANTEED posture.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related