SSD Nodes Learn Hosting plans →
Guides Matt ConnorBy Matt Connor

Cloudflare's security audit skill, sandboxed

Install Cloudflare's security audit skill pinned to a commit, then build the OS-enforced sandbox it needs on a VPS so its leads get tested instead of parked.

What the Cloudflare security audit skill does

The Cloudflare security audit skill turns a coding agent such as Claude Code into a structured security auditor for one codebase. It can read code with no extra setup. To test a lead by running the target's own code, it needs an OS-enforced sandbox. Without one, that lead stays unproven. This guide installs the skill pinned to a commit, then builds that sandbox on a VPS.

The repository is cloudflare/security-audit-skill, released under the MIT licence. An agent skill is a folder with a SKILL.md file. The agent loads it when a task matches the skill's description. How agent skills work covers the format in detail. This skill is large. Next to SKILL.md it ships one reference file per attack area, such as WEB-PROTOCOL-AND-AUTH.md and MEMORY-SAFETY-AND-BINARY.md, plus a JSON schema and two validators.

How the security audit skill works: six phases

The README describes the audit in six phases. Here they are in its own terms:

  1. Reconnaissance: map architecture, trust boundaries, input surfaces, prior evidence and deterministic coverage. The results go into architecture.md and coverage-ledger.json.
  2. Coverage-led hunting: assign isolated hunters from ledger units, record their checks, and use coverage critics to find gaps.
  3. Candidate validation: give every unique candidate to a fresh verifier that tries to disprove it.
  4. Structured output: write confirmed, needs_validation and rejected records to findings.json, validated against report-schema.json.
  5. Independent record verification: fresh agents verify the final source claims. A material replacement receives another independent verifier.
  6. Target-neutral reporting: derive REPORT.md, FINDINGS-DETAIL.md and NEEDS-VALIDATION.md from the verified records.

Some terms need a definition. A sub-agent is a separate agent instance that the main agent starts, with its own context. A hunter is a sub-agent that examines one unit of the coverage ledger, which is the skill's list of areas to cover. A coverage critic is a sub-agent that looks for areas the hunters missed. A verifier is a fresh sub-agent that tries to prove a candidate finding wrong. The README states the rule behind this split: the agent that checks a finding is never the agent that found it.

The validators, validate-findings.cjs and validate-coverage-ledger.cjs, are zero-dependency Node.js scripts. The machine needs Node.js, but no npm packages.

Guidance mode and full audit mode

SKILL.md opens with a plain line: "This skill is guidance by default." Loading the skill does not authorize the full workflow or file creation. A focused question, such as "is this JWT check safe?", gets guidance in the chat. The six phases run only in full audit mode. That needs an explicit audit or pen-test request, a request for a full or end-to-end review, or a request for report files. If your request is ambiguous, the skill asks one focused question first.

A full audit writes to ~/security-audit-skill/<repo-name>/run-<N> by default, where N is the next unused number. That directory sits outside the target repository. The skill writes inside the target only if you choose a directory there and version control ignores that whole directory.

Why the sandbox decides what the audit can prove

Every write-up of this skill quotes one line from its README, and few act on it:

Without these controls, the workflow keeps the lead as needs_validation instead of executing target code.

The README's requirements list an OS-enforced sandbox for "target-controlled builds, tests, processes, browsers, emulators, fuzzers, and fixtures". Target-controlled means the code under audit decides what runs: its build script, its test suite, its fixtures. Some leads can only be confirmed by running that code. For example, proving a parser crash means feeding a crafted input to the real parser. Without a sandbox those leads stay in NEEDS-VALIDATION.md. The report then tells you where to look, but it proves less.

SKILL.md, in its "Universal execution safety" section, lists what the sandbox must enforce:

  • no external network, with only an isolated loopback namespace when a check needs local client and server traffic
  • an empty environment, filled from an explicit allowlist with safe values
  • a read-only target and toolchain, with writes only to per-agent scratch space
  • explicit low CPU, memory, process, file-size, disk and wall-clock limits

It also says not to install dependencies or let builds fetch them. Testing must use dummy principals, fixtures and secrets.

How does the skill know a sandbox is present?

It does not check. SKILL.md, as fetched on 2026-10-25, describes no detection method. There is no probe command, no environment variable and no attestation step. It states the controls and the fallback: if every control cannot be enforced, do not execute target code. The agent then reports the missing sandbox as a needs-validation blocker, with a safe validation plan. The agent itself judges whether the controls hold.

This means the sandbox is your job as the operator. You build it, you prove it holds, and you tell the agent which command is the sandbox. Nothing in the skill will notice if your sandbox leaks.

Wrap the target, not the agent

The agent itself must reach the model API, because every reasoning step is a request to Anthropic. Put Claude Code inside a container with no network and it stops working. So the sandbox wraps the target's builds and tests, and only those. The agent runs outside the sandbox and calls into it.

Three ways to build the sandbox on a VPS

A throwaway VPS or VM per audit

Anthropic's sandbox environments page recommends a dedicated virtual machine, or a cloud session, for work on an untrusted repository. It calls a dedicated VM the strongest separation, because the VM has its own kernel. A fresh VPS per audit gives you that. When the audit ends, you delete the server and anything the target did to it. Running coding agents in a disposable VM covers that pattern.

A throwaway VPS alone does not meet SKILL.md's list. The VPS has a network, because the agent needs one, so a build that runs directly on it can reach the internet. It also has no per-command memory or time limit. A throwaway VPS limits the damage, but a test run directly on it is still unsafe.

A docker run --network none container

Docker's none network driver creates only the loopback device inside the container. That matches SKILL.md's "isolated loopback namespace" allowance. docker run also has flags for most of the rest of the list: --memory, --pids-limit, --cpus, --read-only, --tmpfs, --cap-drop and --security-opt no-new-privileges. docker run has no wall-clock flag, so the wrapper below adds timeout inside the container.

The catch is the docker group. Docker's own post-install guide warns: "The docker group grants root-level privileges to the user." An agent that can call Docker can also start a container without your flags. That is acceptable on a throwaway VPS that holds nothing else. On a server that matters, it is not acceptable.

Claude Code's own sandbox mode

Claude Code has a built-in Bash sandbox. It is off by default. You turn it on with /sandbox, or by setting sandbox.enabled to true. On Linux it uses bubblewrap and needs the bubblewrap and socat packages. Network traffic goes through a proxy that checks each host against allowedDomains, which starts empty.

It falls short of SKILL.md's list in several places. It covers shell commands only: Claude's file tools, MCP servers and hooks run outside it. The sandboxing page, as read on 2026-10-25, lists no CPU, memory, process or time limits. A command that fails in the sandbox can also be retried unsandboxed, unless you set "allowUnsandboxedCommands": false. Anthropic's own wording is that sandboxing "reduces risk but is not a complete isolation boundary". Use it as an extra layer on top of a real sandbox.

What this guide builds

This guide combines the first two options. A throwaway Ubuntu 24.04 VPS holds the agent and limits the damage of any mistake. A small wrapper script, audit-sandbox, runs each target-controlled command in a locked-down container with the controls described below.

Prepare the VPS

Start from a fresh VPS used for nothing else. Install Docker, Git and Node.js, then create an unprivileged user for the agent:

sudo apt update && sudo apt install -y docker.io git nodejs
sudo useradd -m -s /bin/bash auditor
sudo usermod -aG docker auditor
sudo -iu auditor docker run --rm hello-world

The last command should print Hello from Docker!. A permission denied error on /var/run/docker.sock means the group change has not applied. Log the auditor user in again with sudo -iu auditor and retry.

Run the agent as auditor, never as root. Claude Code refuses to start with --dangerously-skip-permissions as root on Linux outside a recognized sandbox, and an agent with root on the box can undo every control you set. Keep your own SSH keys and cloud credentials off this server. Keeping secrets out of AI agents explains why anything the agent can read can leak. To install and log in to Claude Code on the server, follow running Claude Code safely on a VPS.

Install the skill pinned to a commit

The README installs the skill with the Skills CLI:

npx skills add https://github.com/cloudflare/security-audit-skill \
  --skill security-audit

Add --global for a user-level install. That line installs whatever is on main at that moment. As of 2026-10-25 the repository has 14 commits on main and no tags or releases, so a commit SHA is the only thing you can pin to. The commit used here is c1c8a8c1471069fb0e188eeaff69b8e8db6564a8, "Clarify guidance and full audit modes".

The Skills CLI accepts Git refs in source URLs. Here, check out the commit yourself, then install from the local copy. As auditor:

git clone https://github.com/cloudflare/security-audit-skill.git ~/src/security-audit-skill
git -C ~/src/security-audit-skill checkout --detach c1c8a8c1471069fb0e188eeaff69b8e8db6564a8
git -C ~/src/security-audit-skill log -1 --format='%H %s'
mkdir -p ~/.claude/skills
cp -r ~/src/security-audit-skill/skills/security-audit ~/.claude/skills/
ls ~/.claude/skills/security-audit/SKILL.md

git log should print the full SHA followed by Clarify guidance and full audit modes. Claude Code reads personal skills from ~/.claude/skills/<skill-name>/SKILL.md, so the copy is the whole install.

If you prefer the Skills CLI, it accepts a local path as the source. Version 1.7.4 of the skills package declares Node.js >=22.20.0 in its package metadata. Your distribution's Node.js package may be older, so check node --version first. Then pin the CLI version too:

cd ~/src && npx skills@1.7.4 add ./security-audit-skill --skill security-audit -g -a claude-code -y

Either way, the skill on disk now matches one reviewed commit. A new commit or a force-push upstream changes nothing on your server until you move the pin on purpose.

Build the audit-sandbox wrapper

This script runs one command against a read-only copy of the target, in a container with explicit resource limits. Install it as root so the agent cannot edit it:

sudo tee /usr/local/bin/audit-sandbox > /dev/null <<'EOF'
#!/bin/sh
# Run one target-controlled command in a locked-down container.
# Usage: audit-sandbox <target-dir> <command> [args...]
set -eu
target=$(realpath "$1")
shift
exec docker run --rm \
  --network none \
  --read-only \
  --user 65534:65534 \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 2g \
  --cpus 1 \
  --pids-limit 256 \
  --ulimit fsize=536870912:536870912 \
  --tmpfs /scratch:rw,size=512m,mode=1777 \
  --mount type=bind,source="$target",target=/src,readonly \
  --workdir /src \
  "${AUDIT_IMAGE:-debian:12-slim}" \
  env -i PATH=/usr/local/bin:/usr/bin:/bin HOME=/scratch TMPDIR=/scratch \
  timeout --kill-after=10 600 "$@"
EOF
sudo chmod 755 /usr/local/bin/audit-sandbox

Here is how each flag maps to SKILL.md's controls:

  • --network none: no external network, loopback only.
  • env -i with three variables: an empty environment, filled from an explicit allowlist. It clears whatever the image sets, so the command sees only these three.
  • --read-only and the readonly bind mount: a read-only toolchain and target.
  • --tmpfs /scratch with size=512m: limits allocated storage on that mount to 512 MiB. Sparse files can have a larger logical size, so --ulimit fsize=536870912:536870912 separately sets the soft and hard file-size limits to 512 MiB. Each command gets a fresh, empty scratch, which is stricter than per-agent scratch.
  • --memory, --cpus and --pids-limit: the memory, CPU and process limits.
  • timeout --kill-after=10 600: sends SIGTERM after 600 seconds, then SIGKILL ten seconds later if the monitored command is still running. Bare timeout 600 allows SIGTERM to be blocked or ignored. A timeout normally returns status 124; SIGKILL produces status 137. This is a ten-minute timeout with a ten-second grace period, not an exact ten-minute execution bound.
  • --user 65534:65534, --cap-drop ALL and no-new-privileges: the target runs as nobody with no Linux capabilities, and cannot gain more.

Docker also provides private runtime mounts, including /dev/shm, so /scratch is not the only writable location. This wrapper alone does not establish SKILL.md's strict requirement that writes stay inside assigned scratch space. If that requirement cannot be enforced, keep execution-dependent leads as needs_validation.

Docker's documentation notes that data on a tmpfs mount counts toward the container's memory limit. Keep size= well below --memory.

The toolchain image is your job, before the audit

debian:12-slim has a shell and coreutils, but no compiler and none of the target's dependencies. SKILL.md forbids installing dependencies during the audit, and the container has no network anyway. So build an image with the target's toolchain and dependencies before you start the agent, then point the wrapper at it with AUDIT_IMAGE. Do that step yourself, outside the agent. Read what the target's install step runs before you run it.

Prove the sandbox holds before the agent uses it

The skill will not test your sandbox, so you must. As auditor, clone the code you are auditing into ~/audits/target with git clone "$REPO_URL" ~/audits/target. Then run these four checks:

audit-sandbox ~/audits/target ls /sys/class/net
audit-sandbox ~/audits/target touch /src/probe
audit-sandbox ~/audits/target env
audit-sandbox ~/audits/target cat /sys/fs/cgroup/pids.max /sys/fs/cgroup/memory.max

What a healthy result looks like:

  • The first prints only lo. Any other interface means the network is not isolated.
  • The second fails with touch: cannot touch '/src/probe': Read-only file system. If it succeeds, the target is writable, and a malicious test could change the code you are auditing.
  • The third prints exactly PATH, HOME and TMPDIR. An API key or token in this output means it is leaking into the sandbox.
  • The fourth prints 256 and 2147483648, which is 2 GiB in bytes. The value max means no limit was applied.

Run the checks again whenever you change the wrapper or the image.

Run the audit

Start Claude Code in the target directory as auditor:

sudo -iu auditor
cd ~/audits/target
claude

The skill cannot detect your sandbox, so describe the sandbox in the request. Name the wrapper and its controls, including the unresolved scratch restriction:

Run a full security audit of this codebase with the standard profile.
Run every build, test, script or other target-controlled process only through
/usr/local/bin/audit-sandbox /home/auditor/audits/target <command>.
That wrapper gives no network except loopback, an empty allowlisted environment,
a read-only target and toolchain, a fresh /scratch with 512 MiB allocated storage,
a 512 MiB file-size limit, and CPU, memory and process limits. It sends SIGTERM
after 600 seconds and SIGKILL ten seconds later if needed. Docker's private
runtime mounts remain writable, so writes are not restricted to /scratch alone.
If that fails the skill's execution controls, do not execute target code; record
the blocker as needs_validation. Do not run target code any other way, and do
not install dependencies.

The agent follows the wrapper rule by choice. Nothing on the box forces it. Keep Claude Code in a mode that asks before shell commands, and deny any target build or test that does not start with audit-sandbox. Claude Code's permission modes explains the choices. Treat the target's files as untrusted input too. A comment or a README in the audited repository can carry instructions aimed at the agent, which is the problem prompt injection against coding agents describes.

When the run finishes, the results sit outside the target:

ls -d ~/security-audit-skill/*/run-*

The newest run directory should hold REPORT.md, FINDINGS-DETAIL.md, NEEDS-VALIDATION.md and findings.json. Read NEEDS-VALIDATION.md closely, because each entry there is a lead that was not proven. If entries name a missing sandbox as the blocker, the agent did not accept your wrapper. Check your request and run the four checks above again.

What a full audit costs on a Claude plan

Neither the README nor SKILL.md gives a token count for this skill. What you can know is how the cost is built.

Every hunter, critic and verifier is a sub-agent. Claude Code's documentation says a subagent "sends its own requests, which count toward the same usage limits as your main conversation." Each one runs in its own context window, and several can run in parallel. By default Claude Code allows 20 running subagents per session. Spawning another fails with Concurrent subagent limit reached. You can change that cap with CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS. That is a Claude Code limit, and the skill does not set it.

Anthropic's help center says Pro and Max both have a five-hour session limit and a weekly limit, and that these limits are shared across Claude and Claude Code. A full audit therefore draws from the same allowance as your chats. In Claude Code, /usage shows recent usage attributed to skills and subagents, so you can see what one run consumed. How Claude usage limits work explains the five-hour and weekly windows in more depth.

SKILL.md sets no fixed number of hunters. The coverage ledger decides: "one unit is roughly one hunter assignment", and each surviving candidate costs one or two verifier assignments, depending on the profile. A bigger codebase means more units and more sub-agents. You can set a budget in your request. SKILL.md treats it as a cap on agent invocations across all phases. If the budget cannot cover the minimum reconnaissance plus the reserves for critics and validation, no agent runs. If validation cannot cover every candidate, the run is marked incomplete instead of silently going over the budget.

The profiles, as SKILL.md defines them:

  • quick: a bounded pass for small targets, re-runs, or a fast first look, with exactly one hunter wave and one final coverage-critic pass. It must present itself as partial coverage.
  • standard: the workflow as written. This is the default.
  • deep: for high-stakes or large targets, with critic waves run to a clean pass.
  • a scoped run: you limit the audit to part of the codebase, and everything else is recorded as out of scope.

SKILL.md is firm that "Profiles change breadth and redundancy, never the evidence bar." A quick run finds less, but it does not confirm findings on weaker proof.

One more README line matters for cost. Cloudflare reports that, in its test runs, a single run found roughly half of the vulnerabilities that repeated runs found in total. That is Cloudflare's own figure, not an independent measurement. If you want the extra coverage, each extra run draws on the same plan limits.

Audit only code you own or are engaged to test

This is our rule, and Cloudflare does not state it: run this skill only against code you own, or code you have a written engagement to test. The skill's own boundary is technical. SKILL.md tells the agent not to probe deployed endpoints, external services, shared infrastructure or production identities, and to test with dummy principals and secrets. The skill reads code and runs it in your sandbox. The wrapper's --network none makes sure nothing the target runs can reach a live system.

Updating the pin

When Cloudflare pushes new commits, review them before you move. As auditor:

git -C ~/src/security-audit-skill fetch origin
git -C ~/src/security-audit-skill log --oneline HEAD..origin/main
git -C ~/src/security-audit-skill diff HEAD origin/main -- skills/security-audit/SKILL.md

Read the SKILL.md diff in particular, because that file holds the execution safety rules your wrapper was built to meet. If you accept the change, check out the new SHA, copy the folder into ~/.claude/skills/ again, and rerun the four sandbox checks. To use the same pinned skill in several repositories, see sharing agent skills across repos.

FAQ

Does Cloudflare's security audit skill check that a sandbox exists?

No. SKILL.md, as fetched on 2026-10-25, gives no probe, command or environment variable for detecting a sandbox. It lists the controls the sandbox must enforce. It tells the agent not to run target code if any control cannot be enforced. You build the sandbox, verify it yourself, and describe it to the agent in your request.

Why are most of my findings in NEEDS-VALIDATION.md?

The README says that without an OS-enforced sandbox, "the workflow keeps the lead as needs_validation instead of executing target code." Leads that can only be proven by running the target's builds or tests stay unproven. Give the agent a sandbox with no external network, an allowlisted environment, a read-only target and explicit resource limits, and name that sandbox in your request.

Can I run the whole agent inside a container with no network?

No. Claude Code sends every request to Anthropic's API, so an agent with no network cannot work. Put only the target's builds, tests and other target-controlled processes in the sandbox. Leave the agent outside it, on a throwaway VPS or VM.

How much of my Claude plan does a full security audit use?

The skill's README and SKILL.md give no token count. Each hunter, critic and verifier is a sub-agent, and Claude Code's documentation says subagent requests count toward the same usage limits as the main conversation. On Pro and Max those are a five-hour session limit and a weekly limit, shared between Claude and Claude Code. To use less, choose the quick profile or set a budget in your request, which caps agent invocations. Check /usage after a run.

Why pin the skill to a commit SHA instead of a version?

As of 2026-10-25, the repository had no tags and no releases, so there was no version to pin. The README's npx skills add line installs whatever is on main at that moment. Clone the repository, check out a reviewed commit, and copy skills/security-audit into ~/.claude/skills/. An upstream change then reaches your machine only when you choose.

Was this article useful?