40 new critical CVEs before breakfast
Most advisories don't matter for your app. A few matter a lot. How I sort them by how the code actually uses each package, and fix them without breaking anything.
Where it came from: My dependency audits, and the morning an alert reported 40 new critical CVEs.
One October morning my CVE check emailed me: 40 new high or critical advisories since the last baseline. My first instinct was "fix it all." That's the wrong instinct, and it's how you break production on a Monday.
Three buckets
Every advisory goes into one of three buckets, and the deciding question is always how this code uses the package:
- (a) Exploitable. The vulnerable path is reachable from something an attacker controls in production: a request, an upload, a webhook. The clearest example I've had was a spreadsheet library with a known parsing flaw, used to parse files users uploaded.
- (b) Real but mitigated. Reachable only in build tools, dev servers or admin-only paths, or blocked by another layer. Write down what's doing the mitigating.
- (c) Noise. Not reachable, not shipped, or the vulnerable function is never called.
Each verdict cites the file or import that decides it. In my July audit, a long list of advisories came down to five that were actually exploitable. Several were old framework versions with middleware auth bypasses, which almost always land in bucket (a).
Fix in small batches
- Batch 1: patch and minor upgrades that clear the (a) items. Run tests after each repo.
- Batch 2: major upgrades, one at a time, after reading the release notes and manually checking the feature it affects.
- No clean fix? Mitigate instead: validate or sandbox the input, plan a library swap, or remove the code path. I deferred one upgrade because the fix changed how the product's core input format was parsed. That's a product change, not a security patch.
Rules I keep
- Don't edit a repo someone else (or another agent session) is actively working in. Audit it read-only and hand over the list.
- Never run a forced audit fix across everything. It applies major upgrades blindly.
- Commit per repo. Deploy only when I say so.
- Never weaken a security control to make an advisory disappear.
The quiet repos got fixed in one pass, and their advisory count dropped by about a quarter. The active ones got a prioritized list instead of surprise commits.
Steal it
The skill is cve-triage in my John Lab plugin. Paste the audit email and say "triage this."
/plugin marketplace add johnarndt/john-lab
/plugin install john-lab@john-lab/plugin marketplace add johnarndt/john-lab /plugin install john-lab@john-labDownload cve-triage.zipView on GitHub
View SKILL.md
--- name: cve-triage description: "Use when a user gets a pile of security advisories and wants to know which ones matter and fix them safely. Triggers include 'npm audit says 140 vulnerabilities', 'new high/critical CVEs', 'check these advisories', 'are we exposed', 'fix all the vulnerabilities', or a pasted Dependabot or audit email. Sorts every advisory into exploitable, real but mitigated, or noise based on how the code actually uses the package, then fixes in small batches starting with patch-level upgrades." --- # CVE triage An audit tool reports every advisory in the dependency tree. Most do not matter for this app; a few matter a lot. The work is telling them apart from how the code really uses each package, then fixing without breaking the product. ## Workflow 1. **Baseline.** Record the advisory list per repository, the date, and whether the test suite passes. Keep it as the comparison point for the next run. 2. **Classify every advisory:** - **(a) Exploitable:** the vulnerable code path is reachable from input an attacker controls in production (a request, an upload, a webhook, a URL). Example: a spreadsheet parser with a known flaw that parses user uploads. - **(b) Real but mitigated:** reachable only in build tooling, dev servers, admin-only paths, or blocked by another layer. Say what mitigates it. - **(c) Noise:** not reachable, not shipped, or the vulnerable function is never called. Cite the file or import that decides each one. 3. **Prioritize:** (a) first by exposure, then framework and middleware advisories (auth bypasses in the web framework are almost always (a)), then (b). 4. **Fix in batches:** - Batch 1: patch-level and minor upgrades that clear (a) items. Run tests after each repo. - Batch 2: major upgrades, one per change, with release notes read and a short manual check of the affected feature. - When no fixed version exists or the upgrade changes core behavior (for example how the main input format is parsed), propose a mitigation instead: validate or sandbox the input, switch libraries in a planned change, or remove the feature path. 5. **Retest and report:** before/after counts, what was fixed, what was deferred and why, and what needs the owner (a deploy, a decision to replace a library). ## Rules - Repositories under active development by another session or person: audit read-only and hand over the list rather than editing under them. - Commit per repo; do not push or deploy without the owner's go-ahead. - Do not run `audit fix --force` across the board. It applies major upgrades blindly. - Never weaken a security control to make an advisory go away.