Skip to content

/sandbox-lifecycle

The lifecycle gate for a local Codespace-equivalent sandbox. Routed to when the user invokes /ca-sandbox:sandbox to pull an untrusted repo into an ephemeral, host-FS-isolated Docker container, or any of the interaction commands (/ca-sandbox:sandbox-shell,

shell
$ npx -y skills add arbiterForge/codeArbiter --skill sandbox-lifecycle --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/sandbox-lifecycle
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

The lifecycle gate for a local Codespace-equivalent sandbox. Routed to when the user invokes /ca-sandbox:sandbox to pull an untrusted repo into an ephemeral, host-FS-isolated Docker container, or any of the interaction commands (/ca-sandbox:sandbox-shell,

SKILL.md

sandbox-lifecycle.SKILL.md
name: sandbox-lifecycle
description: The lifecycle gate for a local Codespace-equivalent sandbox. Routed to when the user invokes /ca-sandbox:sandbox to pull an untrusted repo into an ephemeral, host-FS-isolated Docker container, or any of the interaction commands (/ca-sandbox:sandbox-shell, /ca-sandbox:sandbox-exec, /ca-sandbox:sandbox-cp, /ca-sandbox:sandbox-destroy) against an existing box. Five gated phases — pre-flight, clone+build, isolated run, interact, teardown. The load-bearing invariant is structural: untrusted code in the box can never reach the host filesystem (no bind mount, no docker socket, never --privileged, cap-drop ALL, non-root, read-only root). Network defaults to offline; egress out is host-initiated only. Every object is labeled ca.sandbox=1 and torn down on exit.

sandbox-lifecycle

Pull an untrusted repo into a throwaway box, explore it without risking the host, then burn the box. This skill owns the whole arc — clone into a named volume, build a dep-cached image, run it under structural isolation, interact (shell / exec / cp out), destroy — and the one invariant that makes it safe: **the code inside the box can never touch the host filesystem.** That guarantee is enforced by construction (no bind mounts, no docker socket, never `--privileged`), not by trusting the repo.

The driver lives in `${CLAUDE_PLUGIN_ROOT}/tools`. The skill never hand-rolls a `docker run` argv — every container is started through `runContainer` in `${CLAUDE_PLUGIN_ROOT}/tools/run.ts`, whose mount argv comes only from `buildMountArgs` in `${CLAUDE_PLUGIN_ROOT}/tools/mounts.ts` (the chokepoint that throws on any bind spec).

Pre-flight

Read these, or STOP and surface the gap — never guess a Docker capability, a mount layout, or an egress posture:

  • `${CLAUDE_PLUGIN_ROOT}/tools/mounts.ts` — the mount-arg chokepoint. Every mount is built here; it throws (`BindMountRejectedError`) on any `type=bind` spec. The structural half of the host-FS invariant.
  • `${CLAUDE_PLUGIN_ROOT}/tools/run.ts` — the isolation flags (`--cap-drop ALL`, non-root `--user 1000:1000`, `--read-only`, `--security-opt no-new-privileges`, resource caps) and the `offline` => `--network none` default.
  • `${CLAUDE_PLUGIN_ROOT}/tools/network.ts` — the network policies (offline / clone-then-cut / allowlist). The IP allowlist is EXPERIMENTAL (`ALLOWLIST_EXPERIMENTAL`); offline and clone-then-cut are the solid defaults.

Host prerequisites: **Docker** and **nixpacks** on `PATH` (the plugin's `description` states this). If `docker info` fails, STOP and report "Docker is not available" — do not proceed to clone or build. If the user supplies no repo URL to `/ca-sandbox:sandbox`, ask for one — do not guess a repo.

Phase 1 — Pre-flight & policy · gate: BLOCK

Establish what is being sandboxed and under what egress posture before any clone:

  • **Target** — the repo URL (or local path) to pull. One source, stated explicitly.
  • **Network policy** — `offline` (default), `clone-then-cut` (fetch deps at build, cut egress at run), or `allowlist` (EXPERIMENTAL — name it as experimental every time it is selected). Default to `offline` unless the user names another.
  • **Docker reachable** — `docker info` returns 0. If not, STOP here.
  • **`--with-claude`** — if requested, route to the `sandbox-claude-inside` skill (`${CLAUDE_PLUGIN_ROOT}/skills/sandbox-claude-inside/SKILL.md`) for its hardened defaults; it is NOT enabled on the default path.

Gate: a named target, a named network policy, and a reachable Docker. A sandbox with no stated target or an unreachable Docker cannot be built — do not improvise either. If `allowlist` is chosen, the BLOCK is conditional on the user acknowledging it is experimental.

Phase 2 — Clone & build · gate: BLOCK

Clone the target into a docker **named volume** (never onto the host FS, never a bind), then build a dep-cached image:

  • Clone into the named volume via `createSandbox` (`${CLAUDE_PLUGIN_ROOT}/tools/create.ts`); the source lives at `/work/repo` inside the box.
  • Build through `${CLAUDE_PLUGIN_ROOT}/tools/build.ts`: nixpacks wraps the repo, deps are relocated **out of tree to `/deps`** (exported via `NODE_PATH`/`PYTHONPATH`/`GOPATH`/`CARGO_HOME`), and the image is tagged `ca-sbx:<repo>-<dephash>`.
  • The dephash comes from `computeDepHash` (`${CLAUDE_PLUGIN_ROOT}/tools/dephash.ts`) over the manifest/lockfile set. An unchanged dep set is a **cache hit** — no rebuild, identical tag. A manifest/lockfile change bumps the dephash and forces a rebuild; a source-only edit does not.

Gate: a built (or cache-hit) image tagged `ca-sbx:<repo>-<dephash>`, with deps at `/deps` (out of tree). The naive "mount the volume over the app dir" layout shadows baked deps and is forbidden — the volume mounts ONLY at `/work/repo`. If nixpacks is not installed, STOP with the install hint, not a stack trace.

Phase 3 — Isolated run · gate: BLOCK

Start the container through `runContainer` (`${CLAUDE_PLUGIN_ROOT}/tools/run.ts`) — never a hand-written `docker run`. The run carries the structural isolation set, all by construction:

  • **No host bind mount, no `/var/run/docker.sock` mount, never `--privileged`** — the three negative guarantees. The mount argv is built only by `buildMountArgs`, which throws on any bind.
  • `--cap-drop ALL`, `--user 1000:1000` (non-root), `--read-only` root, `--security-opt no-new-privileges`, resource caps (`--pids-limit`, `--memory`, `--cpus`).
  • The live source named volume mounts ONLY at `/work/repo`; `/tmp` is a tmpfs (writable scratch, no host backing).
  • Network per Phase 1: `offline` => `--network none`; the richer policies are applied by `${CLAUDE_PLUGIN_ROOT}/tools/network.ts`.
  • Every object carries the `ca.sandbox=1` label (the teardown/registry anchor).

Gate: `docker inspect` on the started container shows no `"Type":"bind"` mount, no docker-socket mount, and not `Privileged:true`. If any of the three appears, the run is rejected — there is no override; the chokepoint

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withcodearbiter

When you can't trust yourself with your code base, trust Arbiter.

Get the whole plugin, auto-invoked
Stats
138
Stars
0
Views
7
Forks
Active
Maintenance
Python
Language
AGPL-3.0
License
5h ago
Last commit
2mo ago
Created

Repo: arbiterForge/codeArbiter

Other skills on codearbiter.