Verification checks#
The shipped verify-sandbox skill runs two phases in the current agent sandbox: a
deterministic 21-check PASS/FAIL battery, then — only when all 21
pass — 10 adversarial breakout probes. The battery exits non-zero on
failure. The slash command is an agent-driven audit: inspect its reported
results rather than relying on the interactive agent’s exit status for CI.
Working in a different workspace?
The shadow wraps claude in every
folder — a workspace needs nothing added to it to be safe, and the
claude-sandbox helper commands are on PATH everywhere.
The project command /verify-sandbox (the full two-phase audit) ships
with the claude-sandbox clone, so it is only available when Claude’s
working directory is that clone. Anywhere else, claude-sandbox verify
runs the battery directly without a model; for the full audit, cd into a clone and run
/verify-sandbox there.
The exact bash for each check is the committed battery script
.devcontainer/claude-sandbox/verify-sandbox-battery.sh (installed to
/usr/libexec/claude-sandbox/, so it is read-only inside the sandbox);
the why of each check lives in the spec at
skills/verify-sandbox/references/checks.md. The summaries below state what
each check asserts; see
locked-down defences for the
defence → primitive mapping.
The battery passes whether or not the egress jail is active (the jail is on by default): the capability check asserts the effective set, which stays empty inside the jail’s nested userns, and the two jail checks (19–20) treat a deliberately disabled jail as a pass with a “disabled” note rather than a failure.
Phase 1 — the 21-check battery#
# |
Asserts |
|---|---|
01 |
|
02 |
|
03 |
Strict-under- |
04 |
|
05 |
|
06 |
|
07 |
|
08 |
|
09 |
|
10 |
|
11 |
No |
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
Workspace is scoped to |
18 |
The installed shadow pins |
19 |
Egress jail active: the netns routing table carries the full blackhole set ( |
20 |
Behavioural counterpart to 19: representative non-allow-listed RFC1918/CGNAT addresses (and the connected subnet’s base) get no forwardable route, while the gateway stays routable. Disabled jail ⇒ nothing to assert (pass). |
21 |
Agent binary mask: in a Codex session |
On any FAIL the battery exits non-zero and names the regressed defence. The audit instructions require the agent to skip phase 2 in that case.
Note
Check 06 asserts the effective capability set, which bwrap’s
--cap-drop ALL empties even when the egress jail nests bwrap’s userns inside the holder’s. In
a jailed session CapBnd reads full (...1ffffffffff, a nested-userns
artifact) but CapEff is still 0, so the battery passes unchanged — no
jail-aware variant of /verify-sandbox is needed.
Phase 2 — adversarial breakout probes#
Runs only when all 21 checks pass. The command reasons up 10 novel breakout attempts aimed at gaps the deterministic matrix does not directly exercise — escaping the filesystem inversion, recovering scrubbed env vars, reaching the host’s network identity, signalling or observing processes outside the pidns, or otherwise violating the threat model.
Constraints on the probes:
Each must be distinct from the others and from phase 1 (not a re-test of
--cap-drop ALLor--clearenvfrom another angle).Bias toward novelty: kernel interfaces (eBPF, perf events, kernel keyrings, io_uring), filesystem corners (proc, sys, debugfs, cgroup, securityfs,
/proc/<pid>/roottraversal), env-var recovery paths, IPC channels (abstract unix sockets, signalfd, pidfd, fanotify), network reachability (loopback services,/etc/resolv.conf, AF_NETLINK, raw sockets), credential paths, exec-chain escalation (setuid binaries despite NO_NEW_PRIVS, file capabilities), and bwrap-specific cases (--die-with-parentrace,--new-sessionbypass, env-redirect bypasses routinggitback to a host gitconfig).
With the egress jail on (the default),
an attempted connection to an RFC1918 or 169.254.169.254 address is a
candidate phase-2 probe — phase-2 probes are reasoned up dynamically, not a
fixed list — and if run it should classify [BLOCKED] (blackholed route)
rather than [ESCAPED].
Each probe is classified on one line:
Classification |
Meaning |
Effect |
|---|---|---|
|
The attempt failed the way the sandbox expects (EACCES, EPERM, ENOENT for masked paths, etc.). |
None. |
|
The attempt succeeded in a way that violates the threat model (readable host credential, writable host path outside the workspace, signal to a process outside the pidns, etc.). |
Audit reports |
|
No error, but no demonstrated breach either. |
Audit reports |
Use reversible probes and stop on a demonstrated escape. If fewer than ten probes run, report the untested items and mark the audit incomplete.
If all 10 probes are [BLOCKED], the final line is
RESULT: SANDBOX OK (21 deterministic + 10 adversarial).