patch

Generate candidate fixes for verified security findings. Consumes TRIAGE.json (preferred), VULN-FINDINGS.json, INCIDENTS.json, or a vuln-pipeline results directory. Pipeline input is delegated to the execution-verified `vuln-pipeline patch` ladder; static-analysis input gets a per-finding patch suba

By anthropics · 978 installs

npx skills add anthropics/defending-code-reference-harness --skill patch

Source repository · Upstream listing

patch Third leg of the static pipeline ( /vuln scan → /triage → /patch ). Turns a ranked list of verified findings into candidate diffs. The skill never applies a diff to the target repo. Output is inert text in ./PATCHES/ for a human to review and apply out of band — see docs/patching.md reviewing generated patches . There is no apply or approve flag by design: the capability isn't present, so it can't be prompt injected into use. Invoke with /patch <findings path [ repo PATH] [ top N] [ id fNNN] [ model M] [ fresh] . Arguments (parse from $ARGUMENTS ): findings path (first positional, required): TRIAGE.json , VULN FINDINGS.json , a pipeline results/<target /<ts / directory, or any JSON the /triage ingest table recognizes. repo PATH : target codebase, read only (default cwd). Required for static mode; the skill stops if cited files don't resolve under it. top N : patch only the N highest severity true positives (static mode). id fNNN : patch only the finding with this id. model M : passed through to vuln pipeline patch in execution verified mode. Ignored in static mode (subagents inherit the orchestrator's model). fresh : ignore ./.patch state/ checkpoint and start over. Tools. Prefer Read, Glob, Grep, Write, Task. Some sessions do not provision Glob or Grep; allowed tools is a permission filter, not a loader. When they are unavailable, fall back to the read only Bash commands whitelisted above: rg / grep for search, ls for enumeration, head / file / wc for sniffing, jq for JSON ingest. Bash is otherwise permitted only for python3 .claude/skills/ lib/checkpoint.py (state I/O) and vuln pipeline patch (execution verified delegate). find is NOT permitted. Write scope. The Write tool may target ONLY paths under ./PATCHES/ and ./.patch state/ . Never write into repo , never git apply , never patch , never edit target source. If a step seems to require it, the step is wrong. Checkpointing (runs before Phase 0 and after every phase) State persists to ./.patch state/ so a fresh /patch session resumes without re spawning patch or reviewer subagents. All checkpoint I/O goes through python3 .claude/skills/ lib/checkpoint.py (atomic, JSON validated). The Write→ from pattern keeps repo derived bytes out of Bash argv; never pass payload via heredoc or stdin. State files: progress.json (single source of truth: {"status": "running" "complete", "phase done": N, "shards done": [...]} ), phaseN.json , chunk.tmp . Start of run. Bash: python3 .claude/skills/ lib/checkpoint.py load ./.patch state status == "absent" OR "complete" , OR fresh in $ARGUMENTS → fresh start. Bash: python3 .claude/skills/ lib/checkpoint.py reset ./.patch state , proceed to Phase 0. status == "running" with phase done == N → resume. Read phase0.json .. phaseN.json in order (and any shard .json listed in shards done ), merge into working state, print Resuming from checkpoint: Phase N complete , skip to Phase N+1. Do not re spawn any subagent whose output is already checkpointed. End of every phase N. Write tool → ./.patch state/ chunk.tmp with the phase's JSON, then Bash: python3 .claude/skills/ lib/checkpoint.py save ./.patch state <N <name from ./.patch state/ chunk.tmp End of run. After writing PATCHES.md and PATCHES.json , Bash: python3 .claude/skills/ lib/checkpoint.py done ./.patch state 4 Phase 0: Parse arguments and detect mode 0a. Parse $ARGUMENTS Extract findings path (first positional), repo (default . ), top , id , model , fresh . If no findings path, stop and ask. 0b. Detect mode Inspect the findings path: execution verified mode when the path is a directory containing reports/manifest.jsonl OR found bugs.jsonl OR run /result.json (pipeline output) — unless an INCIDENTS.json is present anywhere in it: D&R run dirs also contain run /result.json , and their incidents route static. The findings have PoC bytes + ASAN traces + reproduction commands; the pipeline's verification ladder applies. static mode otherwise: TRIAGE.json , VULN FINDINGS.json , INCIDENTS.json (the /dnr output), generic finding JSON, or markdown. No PoC; the oracle is a fresh context reviewer. Record mode in working state. The two modes share Phase 1 ingest then fork at Phase 2. Checkpoint: Write tool → ./.patch state/ chunk.tmp : {"phase": 0, "mode": "exec" "static", "args": {repo, top, id, model, findings path}} Then Bash: python3 .claude/skills/ lib/checkpoint.py save ./.patch state 0 mode from ./.patch state/ chunk.tmp Phase 1: Ingest and normalize Same input contract as /triage Phase 1. Normalize every input format to a flat findings[] of dicts. Pull what's present; never guess what's absent. 1a. Recognized containers (priority order) 1. TRIAGE.json — read .findings[] . Filter to verdict == "true positive" . This is the canonical input: already verified, deduped, ranked, owner tagged. 2. VULN FINDINGS.json — read .findings[] . Unverified; print Warning: VULN FINDINGS.json is unverified scanner output. Consider /triage first. and continue. 3. INCIDENTS.json (the /dnr hunt / /dnr respond output) — same mapping as /triage 1a: one finding per .incidents[] entry, nested vuln.file / vuln.line / vuln.type → file / line / category , description ← vuln.mechanism plus impact , and keep poc — a confirmed exploited incident's poc.command is the regression check for its fix. The top level ruled out array is not findings. Print Warning: INCIDENTS.json has not been triaged. Consider /triage first. and continue. No file incidents follow 1c like any other finding. 4. Pipeline results directory — one finding per reports/bug NN/ . Map report.json → description , crash.crash type → category , ASAN top frame → file / line . Record bug id = NN for the bug N delegate flag. 5. Generic .json with a top level list or a findings / results / issues / vulnerabilities array. 1b. Field aliases (canonical ← also accept) Canonical Also accept file path , location.file , filename line line number , location.line , lineno category type , cwe , rule id , crash type severity severity rating , level , priority title name , summary , message description details , report , body , evidence , rationale recommendation fix , remediation , mitigation owner hint owner , component Attach id ( f001 , f002 , ... in ingest order; preserve existing ids from TRIAGE.json) and source (relative path of the file it came from). 1c. Filter and order If id fNNN : keep only that finding. If top N (static mode): sort by severity HIGH MEDIUM LOW then confidence desc, keep the first N. Drop findings with no file (cannot patch what cannot be located). Record them as skipped with reason "no source location" . 1d. Locate the target codebase (static mode) Resolve repo . For the first 5 findings with a file , check the path resolves under repo (try as given, then with common prefixes stripped). If none resolve, stop : tell the user the cited files aren't reachable and suggest a repo value. Checkpoint: Write tool → ./.patch state/ chunk.tmp : {"phase": 1, "mode": ..., "findings": [...], "skipped": [...], "repo": ...} Then Bash: python3 .claude/skills/ lib/checkpoint.py save ./.patch state 1 ingest from ./.patch state/ chunk.tmp Phase 2: Generate patches Forks on mode . 2A. Execution verified mode — delegate to the pipeline The pipeline already implements the build → reproduce → regress → re attack ladder with executable oracles. Do not reimplement it. For each finding (or once for the whole directory if no id / top filter), Bash: The pipeline writes <findings path /reports/bug NN/{patch.diff, patch result.json} itself. After it returns, Read each patch result.json and copy verdict + rationale into working state. Set verified: "ladder passed" when verdict.passed == true , else verified: "ladder failed" . If the CLI exits non zero (no build command , missing target config), record the stderr as the finding's error and continue with remaining findings. Skip Phase 3 (the ladder is the verifier). Proceed to Phase 4. Checkpoint per finding: Write tool → ./.patch state/ chunk.tmp = {"id": ..., "verified": ..., "verdict": ..., "diff path": ...} , then Bash: python3 .claude/skills/ lib/checkpoint.py shard ./.patch state <id from ./.patch state/ chunk.tmp . After all findings, write the consolidated phase payload to chunk.tmp then: python3 .claude/skills/ lib/checkpoint.py save ./.patch state 2 generate from ./.patch state/ chunk.tmp 2B. Static mode — one patch subagent per finding One Task per finding, all in a SINGLE assistant message for parallel execution. subagent type: "general purpose" . Never set run in background — you need the diff text, not an async handle. Each subagent has read only access to repo . It cannot modify the target; it emits the diff as text in its response. The orchestrator writes that text to PATCHES/bug NN/patch.diff . Patch subagent prompt (assemble once, reuse per finding) fences. <patch diff a/path/to/file +++ b/path/to/file @@ ... @@ context line removed line +added line </patch diff <rationale what changed and why, mechanically — file:line of root cause, what the change enforces</rationale <variants checked file:function pairs you grepped for the same pattern, and whether each needed the fix</variants checked <bypass considered the input variation you tried in step 5 and why it no longer reaches the bad state</bypass considered <test note where the regression test landed, or why none was added</test note If you determine the finding is NOT fixable as described (wrong file, code already patched, finding is a false positive), emit: <patch diff NONE</patch diff <rationale why no patch is appropriate</rationale fences, and HTML escaped entities ( &lt; &gt; &amp; — some runtimes escape angle brackets in notification payloads; unescape before writing the diff). If <patch diff is NONE or empty, mark status: "no patch" . Otherwise write the diff text to ./PATCHES/bug NN/patch.diff (NN = zero padded index in sorted order) and record rationale , variants checked , bypass considered , test note . Checkpoint per finding: Write tool → ./.patch state/ chunk.tmp = {"id": ..., "bug nn": "NN", "status": ..., "rationale": ..., ...} , then Bash: python3 .claude/skills/ lib/checkpoint.py shard ./.patch state <id from ./.patch state/ chunk.tmp . After all findings, write the consolidated phase payload to chunk.tmp then: python3 .claude/skills/ lib/checkpoint.py save ./.patch state 2 generate from ./.patch state/ chunk.tmp Phase 3: Independent review (static mode only) One reviewer subagent per generated diff, all in ONE message, subagent type: "general purpose" . The reviewer never sees the finding's description , recommendation , or the patch author's rationale . It gets only {file, line, category} plus the raw diff bytes, and re derives whether the diff is a minimal, in scope fix by reading the source itself. This keeps any instructions embedded in finding prose from reaching both the author and the gate. Reviewer prompt (assemble once, reuse per diff) Spawn and parse One Task per finding with status != "no patch" . Parse the trailing block. Attach review , style score , out of scope h