IAnswerable

Triage Dependency Vulnerabilities Without Panic

12 May 2026

npm audit reports 40 vulnerabilities and your instinct is either to panic-update everything or ignore the whole list. Both are wrong. Most of those alerts don’t affect you, and figuring out which ones do takes ten minutes, not a security review.

Start with severity, but don’t stop there

npm audit --audit-level=high

This filters out the low/moderate noise, which is most of it. But severity alone is misleading — a “critical” vulnerability in a package whose vulnerable function you never call is not critical to you. Severity describes the CVE in the abstract, not your exposure.

The question that actually matters: is the vulnerable code path reachable?

npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high") | {name: .key, via: .value.via}'

For each result, check: is this a direct dependency, or nested three levels deep inside something you don’t call directly? Run:

npm ls <package-name>

to see the dependency chain. If it’s nested inside a devDependency that only runs at build time and never ships to production — say, a vulnerable transitive dep of your test runner — your actual exposure is close to zero. If it’s in a production dependency handling user input, that’s the one to act on today.

A practical triage table

The severity-then-reachability check above collapses into a simple decision:

flowchart TD
    A["High/critical CVE"] --> B{"Direct prod\ndependency?"}
    B -- No --> C{"devDependency\nonly, no prod exposure?"}
    C -- Yes --> D["Fix on next routine update"]
    C -- No --> E["Watch, don't block"]
    B -- Yes --> F{"Is the vulnerable\nfunction in your\ncall path?"}
    F -- Yes --> G["Fix now"]
    F -- No --> H["Fix this sprint"]
  
Situation Priority
Direct prod dependency, vulnerable function is in your call path Fix now
Direct prod dependency, vulnerable function you never call Fix this sprint
Transitive dependency, no fix available yet Watch, don’t block
devDependency only, no production exposure Fix on the next routine update
“Denial of service” CVE on an internal tool with no public exposure Low priority

Read the actual advisory, not just the headline

npm audit --json | jq '.vulnerabilities["package-name"].via[0]'

GitHub’s advisory database (the source most tools pull from) usually names the specific function or code path affected. A prototype pollution CVE in a templating library only matters if you pass untrusted input into that specific function — many don’t in typical usage.

When there’s no patch yet

Options in order of preference:

  1. Pin to a version just before the vulnerable one was introduced, if the CVE was recently added in a newer release.
  2. Check if a patched fork or drop-in replacement exists — common for abandoned packages.
  3. Mitigate at your layer — if the vulnerability requires a specific input pattern, validate against it at your boundary regardless of the library’s fix status.
  4. Accept the risk explicitly, documented, with a re-check date — not silence, an actual decision written down.

Automate the noise away, not the decisions

Dependabot or Renovate opening a PR for every patch bump is good. Auto-merging every PR without review is not — a patch version bump can still break your build (semver is a convention, not a guarantee). A reasonable middle ground:

# renovate.json
{
  "packageRules": [
    { "matchUpdateTypes": ["patch"], "automerge": true, "matchDepTypes": ["devDependencies"] },
    { "matchUpdateTypes": ["major"], "automerge": false }
  ]
}

Auto-merge patch bumps on dev tooling where a bad update just breaks your local build, not your production system. Keep a human in the loop for anything touching production dependencies, especially major version bumps.

The concrete next action

Run npm audit --audit-level=high right now. For anything critical or high, spend two minutes checking npm ls before deciding it’s an emergency — most of the list turns out not to be one.