Understanding CVSS Scores: How to Prioritize Vulnerability Fixes
A dependency scan hands you a list of CVEs, each with a number attached - 9.8, 7.5, 5.3 - and sorting descending and starting from the top feels like the obvious move. It's a reasonable first pass, but the score alone is missing exactly the context that determines whether a given vulnerability is actually dangerous in your specific application.
What CVSS Actually Scores
The Common Vulnerability Scoring System produces a Base score from eight metrics split into two groups. The Exploitability metrics describe how hard the vulnerability is to actually trigger: Attack Vector (does it require network access, or physical access to the machine?), Attack Complexity (are there conditions outside the attacker's control that have to line up?), Privileges Required, and User Interaction (does a victim have to click something?). The Impact metrics describe what happens if it's exploited: effects on Confidentiality, Integrity, and Availability, plus a Scope metric for whether the impact can spread beyond the vulnerable component itself.
Critically, the Base score describes the vulnerability in the abstract - as published by whoever discovered it, describing the worst reasonable case for a typical deployment. It says nothing about your specific codebase, your specific configuration, or whether the vulnerable code path is even reachable in how you actually use the library.
The Severity Bands
Under CVSS v3.1, the numeric score maps to a qualitative rating: 0.1–3.9 is Low, 4.0–6.9 is Medium, 7.0–8.9 is High, and 9.0–10.0 is Critical. Most scanners surface both the number and the band, and the band is what tends to drive policy ("block any Critical finding from merging") - useful as a blunt automated gate, but it flattens a 9.0 and a 9.8 into the same bucket even though they can represent very different real-world risk once you factor in anything the Base score doesn't capture.
What the Base Score Doesn't Know
- Reachability. A critical deserialization bug in a library only matters to you if your code path actually calls the vulnerable function with attacker-controlled input. A dependency used only for a build-time script that never touches user input carries essentially none of the risk its published CVSS score implies for your application.
- Where it lives. A vulnerability in a devDependency that never ships to production is a different situation than the same score in something running in your request path.
- Existing mitigations. A WAF rule, network segmentation, or an upstream input sanitization step can neutralize a specific vulnerability without the underlying library ever being patched - CVSS has Environmental metrics designed to capture exactly this adjustment, but they require someone to actually calculate and publish them, which in practice almost never happens outside large security teams.
A Better Prioritization Model
Treat the published Base score as a starting filter, not a final ranking, and layer in:
- Whether the affected code is internet-facing or only reachable internally.
- Whether it's in your direct dependency tree or several levels deep in a transitive dependency you don't call directly.
- Whether there's a known exploit actively being used in the wild - this is what EPSS (Exploit Prediction Scoring System) is built to estimate, as a probability rather than a severity, and it's a genuinely useful complement: CVSS answers "how bad would this be," EPSS answers "how likely is someone actually trying this right now." A Medium-severity finding with high EPSS can be a more urgent fix than a Critical one nobody is exploiting yet.
CVSS v3 vs. v4
CVSS v4.0 exists and refines some of the vagueness in v3's scope and impact metrics, along with adding supplemental metrics for things like automatability and recovery effort. As of 2026, v3.1 remains what the overwhelming majority of published CVEs and scanner output still report, so it's the version worth being fluent in even as v4 adoption grows.
Our repo scanner surfaces CVSS severity alongside whether a vulnerable package is a direct or transitive dependency, so prioritization doesn't start from a flat list sorted by number alone. See our comparison of npm audit, Snyk, and Dependabot for how different dependency scanners approach reporting these scores in the first place.
Automate this in your next PR
Run a free repo scan and see outdated dependencies, CVEs, and code quality issues before they ship.
Run a free repo scan