Skip to content

CVE triage skill

A Claude Code skill that turns a fresh CVE or Dependabot alert into either (a) a correctly-bumped dependency with passing tests, or (b) a short report explaining why this alert can’t be auto-fixed and what a human needs to do next.

What this prompt does

Claude reads the CVE number from the issue or alert, looks up the affected package range, finds the vulnerable dep in the repo’s lock file, bumps it to the lowest non-vulnerable version, runs the project’s tests, and opens a PR. If the fix can’t be applied cleanly (transitive dep, major-version bump required, or tests fail), it writes a structured triage note instead and stops — it does not force a patch through.

Inputs: CVE id + optional affected package hint.
Outputs: either a PR (happy path) or a triage note (TRIAGE.md in the working branch).

When to use it

  • A new Dependabot PR landed and failed CI.
  • Someone pasted a CVE into the #security-triage channel and asked for a first pass.
  • Nightly sweep of open advisories that haven’t been looked at in 3+ days.

Don’t use it for:

  • CVEs affecting your own code (SAST findings) — this skill is for third-party dependencies only.
  • Multi-repo fanouts — invoke it per-repo so the guardrails apply.

The prompt

Save as .claude/skills/cve-triage/SKILL.md at the repo root:

---
name: cve-triage
description: |
  Triage a CVE or Dependabot advisory against this repo's lockfile.
  On success, bump the affected dependency to the lowest
  non-vulnerable version, verify tests pass, and open a PR. On
  failure, write a TRIAGE.md note and stop.
---

# CVE triage

## Inputs

All inputs are optional. Infer first from the current session —
the chat message / slash-command arguments, a linked GitHub issue
body, the Dependabot alert payload, the branch name. When nothing
provides a finding id and no scanner MCP is wired in, discover
findings yourself using whatever local tooling is available.

- `CVE_ID`     — e.g. `CVE-2025-12345`. Optional. Search the
                 prompt body and any linked issue for a
                 `CVE-` / `GHSA-` pattern; otherwise skip to the
                 discovery path in step 0 below.
- `HINT`       — (optional) affected package name. Use it as a
                 lookup aid only; the lockfile is the source of
                 truth.

## Discovery path (no CVE_ID provided)

When no id is supplied and no scanner MCP is available:

1. Inventory the repo's lockfiles and manifests.
2. Run the lightest-weight scanner available in the environment,
   in this order: `osv-scanner scan source .`, `npm audit
   --json` (for Node), `pip-audit` (for Python),
   `govulncheck ./...` (for Go), `cargo audit` (for Rust),
   `bundle audit` (for Ruby), `trivy fs --scanners vuln .` as a
   multi-ecosystem fallback.
3. If none are installed, query the GitHub Advisory Database via
   `gh api /repos/{owner}/{repo}/vulnerability-alerts` or the
   public `gh api /advisories?affects=...` endpoint using the
   package names you found in step 1.
4. Pick the highest-severity open finding you can remediate
   under the rules below, and proceed as if its id was passed
   as `CVE_ID`. Note in the PR body that the finding was
   self-discovered and which scanner produced it.
5. If no tooling is available AND no advisory can be fetched,
   stop and write a `TRIAGE.md` explaining what tools the
   environment is missing.

## Procedure

1. **Identify the affected package.**
   - Query the GitHub Advisory Database for `$CVE_ID`.
   - If `HINT` is set, prefer it; otherwise use the advisory's
     reported package name.
   - If there are multiple matching packages in the lockfile, ask
     a human and stop — do not guess.

2. **Confirm the package appears in the lockfile.**
   - For Node: `package-lock.json` / `pnpm-lock.yaml`.
   - For Python: `poetry.lock` / `uv.lock` / `requirements.txt`.
   - For Go: `go.sum`.
   - If not present, write `TRIAGE.md` with "not vulnerable:
     package not in lockfile" and stop.

3. **Determine the fix version.**
   - Lowest version ≥ the patched range reported by the advisory.
   - If the only fix crosses a major-version boundary **and** this
     is a direct dep, stop and write a triage note. Major-version
     bumps require a human.
   - If it crosses a major-version boundary but is a transitive
     dep, follow the project's transitive-dep policy (see
     `docs/security/transitive-deps.md` — if absent, stop).

4. **Apply the bump.**
   - Use the project's package manager — do not hand-edit the
     lockfile.
   - Commit in one change with message `chore(deps): bump <pkg>
     to <ver> (fixes <CVE_ID>)`.

5. **Verify.**
   - Run `make test` (or the project's equivalent). If tests
     fail, do not force through — revert the bump, write
     `TRIAGE.md` with the failing test names, and stop.

6. **Open a PR.**
   - Title: `fix(sec): bump <pkg> to <ver> (<CVE_ID>)`.
   - Body (template): see `.github/PULL_REQUEST_TEMPLATE/sec-fix.md`.
   - Add labels: `security`, `auto-remediation`.

## Guardrails

- **Never** edit code outside the lockfile and the PR body.
- **Never** bypass CI by adding skip tokens.
- **Never** amend commits on branches you didn't create.
- If any `PreToolUse` hook blocks a command, record the block in
  `TRIAGE.md` and stop — do not retry with different commands.

## Outputs

- Success: a pushed branch + opened PR.
- Failure: a `TRIAGE.md` on the working branch with:
  - CVE id
  - Affected package + current / patched versions
  - Reason the automation stopped (copy-pasted, not paraphrased)
  - Suggested next owner (team or individual)

Known limitations

  • Monorepos with multiple lockfiles — the skill stops and asks. Future work: extend step 2 to walk every lockfile in the repo.
  • Native deps pinned to a specific ABI (Node node-gyp native modules, Python wheels) — the bump may succeed and tests may still pass on the runner while failing in prod. Guardrail: the PR description template requires the author to confirm the package is pure-Python / pure-JS.
  • Yanked versions — if the patched version is later yanked from the registry, the skill won’t detect it. Check the upstream release page in the review step.

Changelog

  • 2026-04-21 — v1, first published. Covers Node / Python / Go lockfiles. Monorepo handling d