Last quarter we analyzed patch queues from a handful of early-stage engineering teams — the kind that ship 30 to 60 times per week and run SCA scans on every PR. The pattern was the same everywhere: the top of the queue was dominated by CVSS 9.x advisories in packages that their applications demonstrably never called. The security team knew it. The engineers knew it. Nobody trusted the list, so the list didn't get worked.
This isn't a failure of the teams. It's a structural property of CVSS that most people only half-understand.
What CVSS Actually Measures
The Common Vulnerability Scoring System, now at version 3.1 with v4.0 recently published, was designed to score the severity of a vulnerability in isolation — not the risk that vulnerability poses to your specific application. The distinction matters enormously.
CVSSv3's Base Score is computed from metrics like Attack Vector (AV), Attack Complexity (AC), Privileges Required (PR), and Impact (CIA triad). None of these metrics have a field for "does your application's code actually call the affected function." That's not an oversight — it would be impossible to encode in a universal scoring formula. CVSS is a taxonomy of a vulnerability's theoretical potential, evaluated as if an attacker could always reach the vulnerable code.
The NVD's own documentation says as much: the Base Score "assumes the worst case impact across all possible affected versions." The Environmental Score vector is supposed to let you adjust for your deployment context, but in practice it requires per-CVE manual triage that no team with 300+ advisories per quarter can realistically perform.
The Transitive Dependency Amplification Problem
CVSS wasn't built for the modern package dependency tree. A typical mid-size Node.js application in 2025 has somewhere between 600 and 900 transitive dependencies — packages that your code never directly imports but that are pulled in because something you imported imports them. Your package.json might list 40 direct dependencies. The full tree is 20x that.
When a CVE is published against, say, [email protected] — a utility that parses argument strings — it gets scored based on what that function can do when exploited. The score doesn't consider whether your application passes user-controlled input to it via a code path that's actually reachable from an HTTP endpoint. In most applications, minimist is called only at process startup to parse CLI flags. No HTTP request can reach it. But the CVSS 9.8 in your scanner output doesn't show you that.
We're not saying CVSS 9.8 scores are wrong. They're accurately describing what an attacker could do if they could reach the vulnerable code. The problem is that "if they could reach" is doing enormous hidden work.
What a Score-Based Queue Costs in Practice
Consider a scenario we see repeatedly: a growing SaaS application built in Express.js. The SCA scanner returns 47 CVEs on a given Monday. Of those, 11 have a CVSS base score of 7.0 or higher. The AppSec engineer starts at the top of the list, as any reasonable person would.
CVE-2022-31129 — [email protected], CVSS 7.5, ReDoS via specially-crafted string input. The team spends a morning tracing the code. moment is used in one place: formatting a timestamp on an internal admin report that only runs on a cron job. No user input ever touches it. Non-exploitable in practice — but it took three engineer-hours to confirm that.
Multiply that pattern across 11 high-severity advisories. Your sprint's security budget is gone before you've verified that any of them pose actual risk. Meanwhile, CVE-2021-23337 — [email protected], CVSS 7.2 — is sitting at position 8. Your payment processing endpoint calls _.merge() with user-supplied JSON. That one is exploitable, right now, from the internet. It should have been day one.
The EPSS and KEV Gap
To be fair, the industry has been aware of this problem. FIRST's Exploit Prediction Scoring System (EPSS) attempts to estimate the probability that a CVE will be exploited in the wild within 30 days. CISA's Known Exploited Vulnerabilities (KEV) catalog documents CVEs with confirmed active exploitation. Both are meaningful signals on top of raw CVSS.
The problem is that EPSS and KEV are still population-level metrics. A CVE with EPSS 0.94 is likely to be exploited somewhere — but "somewhere" might be in a code pattern that doesn't exist in your application. A CVSS 9.8 with KEV listing tells you that attackers have figured out how to exploit this in general. It still doesn't tell you whether your specific call graph exposes the vulnerable code.
Reachability analysis fills that gap. It takes the population-level severity signal and asks the only question that matters operationally: does your code actually call this? A call-path trace from your entry points through your dependency tree to the vulnerable function gives you a boolean that CVSS, EPSS, and KEV cannot give you — it either reaches the affected code or it doesn't.
What We See When We Apply Reachability
When Patchlynx runs a reachability scan against a typical Node.js application with 47 SCA advisories, the confirmed-reachable list is usually between 4 and 9 CVEs. That's not because the other 38–43 are low severity — some are CVSS 9.x. It's because the code paths that would expose them don't exist, are behind authentication that sanitizes the input, or are only exercised in environments where the attacker vector doesn't apply.
This is actionable. Your AppSec team works through 6 confirmed-reachable CVEs in a sprint rather than spending the sprint confirming that 11 high-severity CVEs aren't reachable. Your engineers trust the output because the patch list reflects what their code actually does. MTTR for critical-reachable issues drops because the signal-to-noise ratio is honest.
When CVSS Still Matters
None of this means you should stop collecting CVSS scores. They carry real information — particularly the Impact sub-metrics that describe what an attacker can do once they reach the vulnerability. A CVSS 9.8 in a reachable path is categorically different from a CVSS 4.2 in the same path. You need both dimensions.
The way to use CVSS properly is as a secondary sort key within the reachable set, not as the primary filter. Confirm reachability first. Then rank by CVSS + EPSS within the confirmed-reachable list. That ordering reflects your actual exposure, not the theoretical worst case applied uniformly to your entire dependency tree.
The CVSS spec was never lying — it was always doing what it said it does. The mismatch is between what the score measures and the question practitioners need answered. Build your triage workflow around that distinction, and your patch queue becomes a list of things you actually need to fix this sprint.