Open npm audit on a typical legacy app and you see CVEs labelled moderate, high, critical. The labels are CVSS scores. They are computed without knowing anything about your application. Two packages with the same critical-severity CVE can have wildly different real-world risk in your code base. Severity alone is the wrong axis to triage on.

Direct dependencies vs transitive dependencies

A direct dependency is a package you explicitly listed in your manifest (package.json, requirements.txt, go.mod). A transitive dependency is one that your direct dependencies pull in, recursively.

In a typical Node.js app of moderate size: ~25 direct, ~1,200 transitive. The ratio is similar in Python (10–30 direct, ~200 transitive) and Go.

Why depth correlates with real risk

The deeper the package in the dependency graph, the narrower the slice of its surface area you actually call. A vulnerability in a library at depth 1 is, statistically, far more likely to be in code paths you exercise. A vulnerability at depth 5 in a sub-sub-sub-dependency is — again statistically — likely to be in code you never touch.

Depth is not reachability — it is a proxy for reachability. True reachability requires call-graph analysis. Depth is what you can compute in 30 seconds from a lockfile.

The triage matrix

Direct (depth 1)Transitive (depth 2–3)Deep transitive (depth 4+)
CriticalFix now (this week)Fix this sprintFix or document VEX before next release
HighFix this sprintFix next quarterTrack, document VEX
MediumBundle into next bumpTrackTrack only
LowTrackIgnoreIgnore

What an auditor wants to see

An auditor reading your triage report wants the following per CVE:

  1. Severity (CVSS)
  2. Depth in your dependency tree
  3. Whether the vulnerable function is in a code path you actually exercise (the reachable answer; if you can't compute it, depth is the proxy)
  4. Action taken: fixed, scheduled, or VEX-documented as not affected with reason

See depth-aware risk for your tree

DepTriage ranks every CVE by severity × depth × maintainer-health — not just severity.

Run a scan →

FAQ

Is reachability analysis automatable?

For some languages (Java with Soot, JS with bundler-aware analysis) yes, for typical lockfile-based audits no. Depth + maintainer-health is the practical proxy.

What's the difference between depth and "indirect" dependency?

"Indirect" usually means depth ≥ 2 (anything not in your manifest). Depth is the actual integer — useful because depth 2 and depth 6 are very different risks.

Should I dedupe my dependency tree?

Yes. npm dedupe, yarn dedupe, or pnpm's resolver flatten the tree and reduce the number of distinct versions. Fewer versions = fewer CVE-bearing copies.

Keep reading