--- name: security-audit description: Security guidance and vulnerability review for codebases, APIs, services, CLI tools, libraries, and daemons. Use for security questions, focused reviews, vulnerability research, security audits, or pen tests. Run the complete workflow only for explicit codebase audit or pen-test requests, full/comprehensive/end-to-end reviews, or requested report artifacts. --- # Security Audit Find vulnerabilities that violate a real trust boundary, then give owners the source evidence, safe reproduction, priority, and smallest effective fix. This is a defensive, source-first workflow. A candidate without a concrete affected principal, resource, or security outcome is not a confirmed finding. Adapted for DeepSeek Harness from [cloudflare/security-audit-skill](https://github.com/cloudflare/security-audit-skill) (MIT, see `LICENSE-cloudflare`). The attack-class corpus is unmodified upstream text; `DSH-ORCHESTRATION.md` maps the six phases onto this harness's delegation tools. ## Operating modes This skill is guidance by default. Loading it does not authorize the complete audit workflow or file creation. - **Guidance mode**: For security questions, focused reviews, methodology, triage, or investigation of specific findings, use only the relevant parts of this skill. Do not automatically run all six phases, create an output directory, or write audit artifacts. You may launch focused agents when useful; they return results to the current task. - **Full audit mode**: Use the complete workflow when the user explicitly asks to audit or pen-test a codebase, asks for a full, comprehensive, or end-to-end security review, or requests report artifacts. Run all six phases and write the files defined below. If the request could mean either mode, ask one focused question before creating files or starting the complete workflow. ## Harness terminology - **Parent** is this session: the agent that coordinates the run and is the only writer of shared state. - **Task tool** (the upstream term) means this harness's delegation mechanism: the `subagent` tool, or an `agent()` call inside the `workflow` tool. - **`research` agent** is a delegated agent for focused source exploration and factual verification. - **`general` agent** is a delegated agent for broad investigation and bounded local execution. - **`subagent_type:`** in a corpus heading names which of these two delegated roles runs that work; both are single-purpose prompts here, not registered agent types. Delegation rules specific to this harness: - Use `subagent` for a single isolated agent and `workflow` for a wave of many. Both start from a fresh context. - **Never use `subagent_fork` for a hunter, verifier, or critic.** A forked agent inherits this session's reasoning and prior candidates, which destroys the independence the workflow depends on. Forks are acceptable only for work whose value comes from that shared context. - One `agent()` call or one `subagent` call equals **one budgeted invocation**. Count them. - Subagents return text to the parent. They do not update shared files themselves. See `DSH-ORCHESTRATION.md` for the phase-by-phase scripts. ## Execution safety These rules apply in both operating modes. Source inspection is read-only. Run target-controlled builds, tests, processes, browsers, emulators, fuzzers, and fixture processing only inside an OS-enforced sandbox that provides **all** of these controls: - no external network; use only an isolated loopback namespace when the check needs local client/server traffic; - an empty environment populated from an explicit allowlist with safe values, with scratch-local `HOME`, temporary directories, and caches; - a read-only target and toolchain, with the target-controlled process able to write only inside its assigned `scratch/` directory; and - explicit low CPU, memory, process, file-size, disk, and wall-clock limits. **In this harness that sandbox does not exist by default.** DSH's file sandbox (`read-only`, `workspace-write`, `danger-full-access`) governs file writes only. It does not deny network egress, sanitize the environment, restrict the toolchain to read-only, or impose resource limits. A `pwsh` command run from any subagent therefore has full ambient authority — and under `danger-full-access` it can write anywhere the user can. So the default posture is: **read source, do not execute target code.** Treat every behaviour that only execution could establish as `needs_validation` with the exact unresolved fact and a safe plan for the owner. Do not "just try running it" to resolve a lead, and never run an untrusted build, install script, test suite, or fuzzer against the host because a sandbox mode looks reassuring. Only when the user supplies a real OS-enforced boundary — a container or VM with egress blocked, an empty allowlisted environment, a read-only target mount, and resource limits — may bounded local evidence be produced. Verify the boundary before trusting it: attempt egress inside it and confirm the attempt fails, confirm the target mount is read-only, and confirm the limits apply. If any check cannot be run or fails, fall back to read-only analysis. The agent, outside the target-controlled process, may make a disposable source copy in an assigned `scratch/` directory when a build must write beside source. In guidance mode, do not retain target-controlled files. In full audit mode, only trusted parent-side code may promote the minimum non-secret result to retained `artifacts/`. Never expose a retained output directory (other than the agent's own assigned `scratch/`), another agent's directory, the host home directory, credentials, sockets, or shared services to target code. Do not install dependencies or let builds fetch them. Use only tools and dependencies already available locally. Use dummy principals, fixtures, and secrets. Do not probe deployed endpoints, external services, shared infrastructure, production identities, other users' data, or live control planes. Do not test availability against a live or shared process, publish artifacts, alter releases, spend paid API quota, or continue beyond the minimum local effect needed to establish a defect. If the decisive fact is outside source or the sandboxed fixture, report it as needing validation. ## Full audit setup In full audit mode, resolve these values before reconnaissance: - **Skill directory**: the absolute directory containing this `SKILL.md` (run the validators from here). - **Target**: the absolute repository root under review. - **Repo name**: a stable repository identifier from the directory or local Git remote. - **Output directory**: a new writable directory outside the target, defaulting to `~/security-audit-skill//run-`, where `` is the next unused integer. Use a directory inside the target only when the user explicitly selects it and the parent verifies that version control ignores the whole directory. Otherwise stop and request an external path. - **Source ref**: the reviewed commit and whether the worktree is dirty. Do not treat unreviewed generated or modified files as another revision. ### Write isolation The parent creates and is the only writer of shared run files: - `run-metadata.json` - `architecture.md` - `coverage-ledger.json` - `findings.json` - `REPORT.md` - `FINDINGS-DETAIL.md` - `NEEDS-VALIDATION.md` Each hunter or verifier receives a unique root under `/agents//`, with separate `scratch/` and `artifacts/` directories. Canonical agent IDs match `^[a-z0-9][a-z0-9_-]{0,63}$` and must not equal a Windows device name such as `con`, `prn`, `aux`, `nul`, `com1` through `com9`, or `lpt1` through `lpt9`. Lowercase IDs prevent case-fold collisions. Subagents share this machine's filesystem, so isolation here is **enforced by instruction plus parent-side promotion**, not by an OS boundary. State the assigned root in every prompt, forbid writes outside `scratch/`, never pass a shared file or another agent's root to a subagent, and re-verify on disk that a returned artifact is inside the directory you assigned before reading it. Agents may not change shared files, target source, retained `artifacts/`, or another agent's directory. Do not use `/tmp` or the host home directory as a writable fallback. Promotion to retained `artifacts/` is parent-only: validate the relative path (reject absolute, empty, `.`, `..`, symlinked or reparse components), require a regular file within the declared byte limits, copy it, and re-check size and identity. Never recursively copy or glob scratch, extract an archive into artifacts, or promote a symlink, junction, FIFO, socket, device, directory, hard-linked file, or changing file. If any check fails, discard the entry; if it was decisive, keep `needs_validation` with the exact promotion blocker. For a reproduced check, record the command, exact test input, sandbox limits, and only the allowlisted environment variable names plus safe non-secret values needed to reproduce it. Never capture or copy the ambient environment, inherited variables, credential values, authentication state, or unrelated host paths. Launch from an empty environment rather than trying to redact one after execution. Before delegation, the parent writes `run-metadata.json` with at least `run_id`, `repo`, `target`, `source_ref`, `profile`, `scope_paths`, `budget` (null if unset), `execution_policy: "sandboxed-source-and-local-only"`, selected companion files, prior-run paths, shared-file owners, and `run_status: "in_progress"`. Update metadata only when those facts change; candidate state belongs in the coverage ledger and `findings.json`. ## Full audit planning The coverage, prior-run, profile, and budget requirements in this section apply only in full audit mode. ### Coverage and prior runs No one pass is complete. Build a deterministic coverage plan before hunting and update it after every agent result. `RECONNAISSANCE.md` defines the stable coverage units and `HUNTING.md` defines coverage-critic waves. The parent alone updates the ledger. If prior runs exist, read every compatible `coverage-ledger.json` and `findings.json` before planning the current run: 1. Compare the relevant current source with each prior record and unit. A prior source ref alone is not evidence that a path is unchanged. 2. Carry a prior `confirmed` record into the current candidate set only when its relevant source and conditions are unchanged and its evidence still meets the current contract. Link it to a current ledger unit seeded `planned`, preserve its fingerprint, exclude only that carried root cause from hunters, and send the carried record through the current final verification path; the Phase 3 verifier that re-checks it becomes that unit's assignment owner and moves it to `candidate`. 3. When relevant source for a prior `confirmed` record changed, create a current planned revalidation unit. Do not put that record on the hunter exclusion list. It remains confirmed only if current independent validation establishes the current path and result. 4. Make prior `needs_validation`, `deferred`, `blocked`, `out_of_scope`, and any changed-source unit current work. A still-external `needs_validation` record may be carried only after the current source trace is checked and linked by fingerprint to a current `planned` unit whose verifier re-check supplies its owner and evidence; the record keeps the unresolved blocker. These prior states never suppress a current unit. 5. A prior same-source covered unit may inform priority, but it remains visible in the current ledger. A prior `rejected` record suppresses only the unchanged failed claim, not coverage of its unit; changed evidence creates current work. 6. Read the prior profile and scope. A prior `quick` or scoped ledger contributes only its recorded evidence and gaps, never an implied "rest is fine." If no prior ledger exists, say so in the final coverage statement. Never imply that one run exhausts the target. ### Run profiles and scope During full audit setup, pick a profile from the user's request or propose one from the target's size and stakes. Record it in `run-metadata.json` (`profile`, `scope_paths`) and state it in the report. The default is `standard`. - **`quick`** — a bounded pass for small targets, re-runs, or a fast first look. Coarsen ledger units to surface × boundary × attack class (subsystem uses the fixed canonical `profile/quick/all-in-scope-subsystems` identifier), run exactly one hunter wave followed by exactly one final coverage-critic pass, and use one fresh verifier per candidate for both candidate validation and final record verification. Do not launch a follow-up hunter wave: record the critic's accepted discoveries and reassignments as `deferred`. - **`standard`** — the workflow as written. - **`deep`** — for high-stakes or large targets. Split ledger units per subsystem and lifecycle mode, run critic waves to a clean pass, keep candidate validation and final record verification as separate fresh agents, and give `prior_covered_same_source` units an independent second pass. A **scoped run** audits a subset: named paths, one subsystem, one companion domain, or the diff between two source refs. Seed ledger units only for in-scope surfaces and record everything else as `out_of_scope` — never as `covered`. A scoped or `quick` run must present itself as partial coverage. Profiles change breadth and redundancy, never the evidence bar. Do not scale away the candidate gate, the source/local execution boundary, `needs_validation` discipline, schema validation, or independent verification of `confirmed` records. #### Cost budget The ledger makes spend countable: one unit is roughly one hunter assignment, and one surviving candidate is one or two verifier assignments depending on profile. When the user sets a budget — or the parent proposes one for a large target — record `budget` in `run-metadata.json` as a maximum number of `agent()`/`subagent` invocations across all phases. Apply the strict budget gate before launching any reconnaissance agent. Reserve the four baseline reconnaissance calls, one final post-wave critic for `quick` or one post-wave plus one distinct final-clean critic for `standard`/`deep`, and at least one verifier call. Add focused reconnaissance only after repeating this gate for each extra call. If the requested budget cannot fund that minimum, launch no agent: ask for a larger budget, narrower scope, or different profile. If the request remains unchanged, set `run_status: "incomplete"` with `incomplete_reason: "budget_cannot_fund_reconnaissance_and_reserves"` and report that no audit pass ran. Spend it in this order: 1. Count reconnaissance, every post-wave critic, and the separate final-clean critic as agent invocations. 2. **Reserve critics and validation before hunting.** For `quick`, reserve its one post-wave final critic. Before every `standard` or `deep` hunter wave, reserve one immediate post-wave critic plus one distinct final-clean critic. Also reserve verifier cost from the profile (about 1 or 2 agents per expected candidate; when in doubt reserve 30% of the balance after critic reservation). Never assign hunters into either reserve. 3. Assign hunters to units in priority order until the hunting allowance is spent. Spend the reserved post-wave critic immediately after that wave; keep the final-clean and validation reserves intact. 4. Before a later wave, reserve its new post-wave critic again. If the remaining budget cannot cover the required critic calls and validation reserve, launch no hunters from that wave, mark its planned units `deferred` with reason `budget_cannot_reserve_critics_and_validation`, and use the retained final-clean critic to record the resulting gap. Before wave 1, update the pre-recon estimate with seeded units, implied hunter count, mandatory critic calls, validation reserve, and whether the remaining budget covers the plan. If it clearly cannot, say so and propose either a tighter scope or a coarser profile instead of silently thinning evidence. If later facts consume the required final-critic reserve, launch no hunters, mark all planned work deferred, set the run incomplete with reason `critic_budget_exhausted`, and make no complete-coverage claim. A strict total-agent budget can still be exceeded by an unexpectedly large candidate set or by a material Phase 5 replacement that needs another independent verifier. If the remaining budget cannot validate every candidate, stop hunting, validate candidates in fingerprint order while the budget permits, and set `run_status: "incomplete"` plus `incomplete_reason: "validation_budget_exhausted"`. Keep each unvalidated fingerprint linked to a `candidate` ledger unit with that unresolved reason. Do not put an unvalidated candidate in `findings.json`, relabel it `needs_validation`, or report the run as complete. Phase 6 may produce a partial report only if its first section states that candidate validation is incomplete and lists the affected fingerprints and units. Never exceed a user-set strict budget silently. ## Core principles ### Require a boundary and result For every candidate, name the lower-trust principal, accepted input or action, intended control, crossed boundary, affected principal or resource, and concrete observed or owner-observable result. Do not elevate a missing best practice, guessed deployment behavior, generic parser crash, or self-impact into a security finding. ### Use bounded local evidence Static analysis establishes the source path. Sandboxed local tests resolve behavior **only when all execution controls listed under Execution safety are actually verifiable**; a minimal function harness, existing unit test, small parser fixture, dummy-tenant integration test, locally rendered configuration, or bounded isolated-loopback client. Stop at a wrong return value, unauthorized dummy record, sanitizer finding, policy difference, or other minimum effect. Do not extend the local check beyond the minimum boundary result or produce persistence, post-fault, or concealment material. In this harness, absent a verified OS-enforced boundary, this section permits reading code, not running it. ### Respect source visibility Deployment controls, proxy behavior, provider settings, browser headers, identity policy, broker ACLs, packaging, and topology are real controls. If they are required and absent from the repository, do not assume either presence or absence. Use `needs_validation` with the exact missing fact and a safe owner-observed or local plan. ### Separate priority from certainty Only `confirmed` records receive severity. Likelihood and impact must reflect the demonstrated conditions and result; overall severity cannot exceed demonstrated impact. `needs_validation` means a specific source-grounded boundary hypothesis is blocked, not a low-confidence confirmed vulnerability, and it has no severity. Calibrate overall severity with these anchors: - **critical** — an unauthenticated actor gains code execution, full data-store access, or takeover of arbitrary accounts. - **high** — an actor fully defeats an explicit security control with real consequences: authentication bypass, cross-tenant read or write, stored script execution affecting other users, authenticated code execution, or an unauthenticated remote stop of a shared service. - **medium** — a real boundary violation with limited blast radius, uncommon preconditions, or consequences confined to a narrow resource set. - **low** — disclosure of non-secret internals, or an effect requiring sustained effort for minimal gain. - **informational** — a confirmed but minimal-impact observation, useful mainly as a prerequisite inside a larger finding. The high/medium discriminator: does the demonstrated result fully defeat an explicit control for an action with real consequences, or only weaken it? If you cannot state the concrete damage, the severity is lower than it feels. ### Recommend the smallest effective source fix For each confirmed finding, identify the invariant the code must enforce and the narrowest source change that enforces it at the last trusted decision point. Prefer specific repository-relative changes and regression tests over generic hardening advice. The audit describes fixes; it does not modify target source. ## Full audit workflow In full audit mode, follow all six phases in order, using the orchestration scripts in `DSH-ORCHESTRATION.md`: 1. **Reconnaissance** — map the source, trust boundaries, local build paths, companion selections, prior evidence, and initial deterministic coverage ledger with `RECONNAISSANCE.md`. 2. **Coverage-led hunting waves** — assign isolated hunters from the ledger and collect structured candidate results with `HUNTING.md`, `ATTACK-CLASSES.md`, and the selected domain companions. 3. **Candidate validation** — consolidate fingerprints and give every candidate to a fresh source verifier as defined in `VALIDATION-AND-REPORTING.md`. 4. **Structured output** — write all final `confirmed`, `needs_validation`, and `rejected` records to `findings.json`; validate it against `report-schema.json` and `validate-findings.cjs`, and validate the coverage claim with `validate-coverage-ledger.cjs`: On Linux/macOS: ```bash node "/validate-coverage-ledger.cjs" "/coverage-ledger.json" node "/validate-findings.cjs" "/findings.json" ``` **The validators do not run on Windows.** They require POSIX `O_NOFOLLOW`/non-blocking opens and refuse to read the file, reporting `OS no-follow and nonblocking input protection is unavailable`. On Windows run them in a Linux container, copying the inputs inside first — a Windows bind mount reproduces the same unsafe-open failure: ```powershell docker run --rm -v ":/skill:ro" -v ":/out:ro" node:22-alpine ` sh -c "cp -r /skill /tmp/skill && cp /out/coverage-ledger.json /out/findings.json /tmp/ && node /tmp/skill/validate-coverage-ledger.cjs /tmp/coverage-ledger.json && node /tmp/skill/validate-findings.cjs /tmp/findings.json" ``` Both exit non-zero on failure. Do not hand-repair a passing report; fix the record. Do not treat "the validator cannot run here" as a pass, and do not silently skip the gate — an environment that cannot run it makes the run `incomplete` with that exact reason. 5. **Independent record verification** — use fresh agents to verify final source claims and reconcile corrections or state changes. 6. **Target-neutral report** — derive `REPORT.md`, `FINDINGS-DETAIL.md`, and `NEEDS-VALIDATION.md` from the final records, with no live-probe instructions. Do not end the run before one of exactly two terminal states: (a) all Phase 6 artifacts are written and both validators pass, or (b) `run_status: "incomplete"` is recorded with its exact reason and the gap is disclosed in the report. Never stop mid-phase. ## Anti-patterns 1. Checklist deviations presented as vulnerabilities. 2. Defense-in-depth advice with no reachable boundary violation. 3. Live or shared-environment testing where bounded local evidence is insufficient. 4. Guessing provider, proxy, browser, identity, or deployment behavior not present in source. 5. Treating intended same-principal authority or self-impact as a cross-boundary result. 6. Reporting a parser or runtime effect stronger than the observed effect. 7. Emitting prose-only hunter results that cannot be deduplicated or verified. 8. Re-reporting carried same-source prior confirmed records or using them as exemplars that anchor the hunt. 9. Assigning severity to `needs_validation` records. 10. Writing the report before independent verification or letting prose and JSON disagree. 11. Running a target build, test, or installer on the host because the file sandbox looked sufficient. 12. Verifying a finding with `subagent_fork`, or with any agent that has seen the parent's candidate list. 13. Letting a subagent write shared run files or another agent's directory.