---
name: cve-remediation
description: >-
  Remediate dependency vulnerability scanner failures by verifying live
  package registry data and upgrading instead of suppressing. Use when an
  SCA / CVE tool fails or files an alert: npm audit, pnpm audit, yarn audit,
  pip-audit, safety, Snyk, Dependabot, Trivy, Grype, OSV-Scanner, audit-ci,
  cargo audit, govulncheck, bundler-audit, Renovate, or any CI step
  reporting a CVE / GHSA / OSV advisory. The first action is ALWAYS to look
  up the latest published version on the live registry — training data is
  months stale and almost never knows the current version. Suppression,
  ignore, or allowlist entries are the absolute last resort, only after
  upgrade and override paths are proven unavailable; surrounding GitHub
  issues and justification docs do not by themselves count as remediation.
  Do NOT use for vulnerabilities in your own application code (use security
  skill), general linter or type checker bypasses (use max-quality-no-shortcuts
  skill), or unrelated CI failures (use ci-debugging skill).
metadata:
  author: Geoff
  version: 1.0.0
---

# CVE Remediation

A dependency vulnerability scanner just failed. The fix is almost always a version bump — but you must look up the current version on the live registry, because your training data is months out of date and the version you remember is probably not the latest.

## The Anti-Pattern to Resist

When a CVE alert fires, the path of least resistance feels like:

1. Add a suppression / ignore / allowlist entry.
2. File a GitHub issue.
3. Write a justification doc.
4. Move on.

**Do not start here.** This sequence is correct only as a final fallback after upgrade paths are demonstrably exhausted. Treating it as the default leaves real, fixable vulnerabilities in production behind a paper trail of issues that nobody revisits. Polished documentation around a suppression is not a fix.

## Instructions

### Step 1: Parse the Alert

Extract from the scanner output:

- Package name and ecosystem (npm, PyPI, crates.io, Go module, Maven, RubyGems, NuGet, Composer)
- Currently installed version
- Advisory ID (`CVE-YYYY-NNNNN`, `GHSA-xxxx-xxxx-xxxx`, `OSV-YYYY-NNNN`)
- Severity (critical, high, moderate, low)
- "Patched versions" range (e.g., `>= 1.4.2`)
- Whether it is a direct or transitive dependency

If any field is missing, look it up before acting. Do not remediate from a half-read alert.

### Step 2: Look Up the LATEST Published Version (Critical)

**You do not know the current version.** Your training data trails reality by months and most ecosystems publish daily. Whatever version you "know" is the latest is probably wrong. Always query the live registry first.

| Ecosystem | Quick command |
|-----------|---------------|
| npm / pnpm / yarn | `npm view <pkg> version` |
| PyPI              | `pip index versions <pkg>` |
| crates.io         | `cargo search <pkg> --limit 1` |
| Go modules        | `go list -m -versions <module>` |
| Maven Central     | see `references/registry-lookups.md` |
| RubyGems          | `gem search -re <pkg>` |
| NuGet             | see `references/registry-lookups.md` |
| Packagist         | `composer show <pkg> --available --no-interaction` |

See `references/registry-lookups.md` for HTTP fallbacks when the package manager CLI is not available, and for listing the full version history.

### Step 3: Verify the Latest Version Actually Fixes the CVE

A newer version is not automatically a patched version. Confirm the fix:

1. Compare the latest registry version against the advisory's "patched versions" range. If `latest >= fixed-in`, upgrading fixes it.
2. Read the changelog / release notes for the version that introduced the fix.
3. For GHSA: `gh api /advisories/<GHSA-id> --jq '.vulnerabilities[].patched_versions'`
4. For OSV: `curl -s https://api.osv.dev/v1/vulns/<id> | jq '.affected[].ranges'`

If the latest version is still vulnerable, the CVE is genuinely unpatched. Skip to Step 6.

### Step 4: Apply the Upgrade

**Direct dependency** — bump the version constraint, regenerate the lockfile, test:

```bash
npm install <pkg>@<fixed-version>
uv add "<pkg>>=<fixed-version>"
poetry add "<pkg>@^<fixed-version>"
cargo update -p <pkg> --precise <fixed-version>
go get <module>@<fixed-version> && go mod tidy
bundle update <pkg> --conservative
```

**Transitive dependency** — find the direct dependency that pulls it in, and either bump that direct dependency to a version whose tree includes the patched transitive, or override the transitive directly:

| Ecosystem | Override mechanism |
|-----------|--------------------|
| npm       | `"overrides"` in `package.json` |
| Yarn      | `"resolutions"` in `package.json` |
| pnpm      | `"pnpm.overrides"` in `package.json` |
| pip       | constraints file via `pip install -c constraints.txt` |
| Poetry / uv | direct entry in `[tool.poetry.dependencies]` / `[project.dependencies]` |
| Cargo     | `[patch.crates-io]` in `Cargo.toml` |
| Go        | `replace` directive in `go.mod` |
| Bundler   | explicit entry in `Gemfile` |

Confirm the override took effect:

```bash
npm ls <pkg>
pip show <pkg>
cargo tree -i <pkg>
go mod why <module>
```

### Step 5: Verify the Fix

1. Re-run the scanner that failed. It must pass.
2. Run the project's test suite, type checker, and linter. Upgrades can introduce breakage.
3. For major version bumps, read the upstream changelog and adapt callers as needed.

If tests break, fix the breakage. Do not roll back the upgrade unless the breakage is genuinely insurmountable — and if it is, treat the upgrade as blocked and escalate, do not silently suppress.

### Step 6: Last Resort — Suppression (Only If Truly Unfixable)

You may add a suppression / ignore / allowlist entry only if ALL of these hold:

- [ ] Step 2 confirmed: no patched version exists on the live registry yet.
- [ ] Step 3 confirmed: the latest published version is still vulnerable.
- [ ] Upstream maintainers have a tracking issue or PR you can link to (or, if not, you have opened one).
- [ ] You have considered and rejected: forking, applying a `patch-package` / `cargo patch` / equivalent local patch, swapping libraries.
- [ ] You have explicitly assessed exposure: severity × reachability × runtime context.

If all five hold, suppress using `references/suppression-template.md`. The suppression must include:

1. Advisory ID and link
2. Why no upgrade exists, with link to upstream tracking
3. Reachability and exposure assessment
4. Concrete review date, no more than 30 days out
5. Linked GitHub issue tracking re-evaluation

A bare `# noqa`, `audit-ci.json` allowlist, or `.snyk` entry without these is incomplete. Reject your own work if any of the five is missing.

### Step 7: Commit and Document

- Upgrade commit: `fix(deps): bump <pkg> to <version> for <CVE-id>`
- Suppression commit: include the GitHub issue number, and the issue must encode the review date as a label or due date so it is rediscoverable.

## Examples

### Example 1: Direct npm Dependency, Patched Version Available

**Alert**: `lodash 4.17.20` has CVE-2021-23337 (high). Fixed in `>= 4.17.21`.

```bash
$ npm view lodash version
4.17.21
$ npm install lodash@4.17.21
$ npm audit                # passes
$ npm test                 # passes
```

Commit: `fix(deps): bump lodash to 4.17.21 for CVE-2021-23337`.

### Example 2: Transitive Python Dependency

**Alert**: `urllib3 1.26.5` (transitive via `requests`) has GHSA-v845-jxx5-vc9f. Fixed in `>= 1.26.18`.

```bash
$ pip index versions urllib3
Available versions: 2.2.3, 2.2.2, ..., 1.26.20, 1.26.18
$ pip show requests | grep Requires      # confirms requests pins urllib3<2
$ uv add "urllib3>=1.26.18,<2"
$ uv lock && uv sync
$ pip-audit                              # passes
```

### Example 3: No Patch Available — Genuine Last Resort

**Alert**: `obscure-lib 0.3.1` has CVE-2025-XXXXX. No fixed version listed.

```bash
$ npm view obscure-lib version
0.3.1                                    # latest is still vulnerable
$ gh api /advisories/GHSA-xxxx-yyyy-zzzz --jq '.vulnerabilities[].patched_versions'
""                                       # no patch exists
$ gh search issues --repo upstream/obscure-lib "CVE-2025-XXXXX"
# Found: upstream/obscure-lib#42 — fix in progress
```

Open project issue, link to upstream issue #42, set review date 30 days out, suppress using the template in `references/suppression-template.md`.

## Troubleshooting

### Error: "I'm sure version X.Y.Z is the latest"

You are not. Run the registry lookup command from Step 2. Training data lag is the single most common reason this skill exists.

### Error: "The override didn't change the resolved version"

Override syntax wrong, or lockfile not regenerated. After editing the override:

- npm: delete `node_modules` and `package-lock.json`, then `npm install`
- pnpm: `pnpm install --force`
- Cargo: `cargo update`
- Verify with `<package-manager> ls <pkg>` or `cargo tree -i <pkg>`.

### Error: "Tests break after the upgrade"

Not a reason to suppress. Read the changelog, fix the breakage. If a major version jump is too disruptive right now, pin to the highest minor / patch within your current major that contains the fix.

### Error: "The vulnerable code path is unreachable from our app"

Reachability is supporting evidence for prioritization, never a substitute for upgrading when a fix exists. Reachability arguments belong only in Step 6, when no fix exists at all.

### Error: "Just adding the suppression and an issue is faster"

Yes, and the suppression has no expiry the tooling enforces. Every "temporary" suppression added without an enforced review date is a permanent unfixed CVE wearing a costume.
