You inherited the codebase last week. The compliance review is in three weeks. npm audit just printed 1,873 vulnerabilities. Your security officer is asking for "the list" by Friday. You don't have the option to spend six months refactoring.
This is the single most common scenario I see in dependency-triage support tickets. Here is the playbook that works under deadline pressure — written by someone who has had to deliver "the list" in 72 hours.
Step 1 — Stop reading the npm audit output
The output of npm audit on a typical legacy app is, in practice, noise. It dumps every CVE for every package without context: a critical CVE in a package only used by your test runner reads the same as a critical CVE in your authentication library. You cannot triage 1,873 lines manually. Don't try.
Step 2 — Build the actual graph
The first useful artifact is a tree where each vulnerable package has a depth:
- Depth 1 — direct dependency (in your
package.json) - Depth 2 — pulled in by one of your direct dependencies
- Depth 3+ — deep transitive (often only reachable in narrow code paths)
For a triage report, depth correlates with reachability and therefore real risk. A critical CVE at depth 1 is something you depend on directly and certainly call. A critical CVE at depth 5 inside a build-time-only sub-dependency is far less urgent.
Rule of thumb under deadline: fix everything Critical/High at depth 1–2. Document everything depth 3+ in the SBOM with a VEX statement. Ship.
Step 3 — Categorize before you fix
Group every package into one of four buckets:
| Bucket | Action |
|---|---|
| Reachable + actively maintained | Bump to fixed version. Test. Ship. |
| Reachable + abandoned | Replace package or fork. This is the painful work — schedule it explicitly. |
| Not reachable + maintained | Bump opportunistically. Not blocker. |
| Not reachable + abandoned | Document in VEX as not_affected, code_not_present. Ship. |
Step 4 — The 80/20 fix list
In every audit I've done, fewer than 30 packages — out of 1,000+ — actually need urgent action. The triage list usually looks like this:
- 3–5 critical-and-direct packages: bump and test
- 5–10 critical-and-transitive-but-reachable:
npm dedupe, then resolutions/overrides - 1–3 abandoned-and-load-bearing: schedule replacement (this is what you escalate to product/management)
Step 5 — Produce the deliverable auditors actually want
A compliance reviewer is not going to read 1,873 GitHub advisory pages. They want:
- A signed CycloneDX SBOM of the shipped build
- A VEX (Vulnerability Exploitability eXchange) document explaining which CVEs are not exploitable and why
- A short narrative ("Of 1,873 alerts, 18 require remediation; 12 are remediated in this release; 6 are documented as not affected via VEX") — one page
Generate the SBOM + triage list in 30 seconds
Paste your package-lock.json. Get a depth-ranked report and a CycloneDX SBOM ready to hand to the auditor.
FAQ
No. Mass-upgrading 1,000 packages on a deadline is how production breaks at 2 AM. Upgrade the small set with active CVEs, ship that, then schedule a separate cleanup sprint.
npm audit fix --force?Avoid on legacy code. The --force flag installs major-version bumps that frequently change APIs. You will spend more time chasing regressions than triaging the original CVEs.
Reachability analysis is the open research problem. Pragmatic proxies: depth in the graph, whether your direct dependency calls into the vulnerable function, and whether the CVE's affected functions appear in your bundle. DepTriage uses depth + maintainer-health as its reachability proxy.
Yes — VEX is recognized by the EU CRA, NTIA, and major procurement frameworks. The document has to be honest (you have to actually have analyzed why the code is not affected), but it is a legitimate response, not a workaround.