# Common Vulnerability Scoring System (CVSS)

> CVSS is FIRST's open standard that turns a vulnerability's attack conditions and impact into a 0.0 to 10.0 severity score. It measures severity, not risk.

Also known as: Common Vulnerability Score, CVSS score, CVSS v4.0, CVSS v3.1

Source: https://socket.dev/glossary/common-vulnerability-scoring-system

Last updated: 2026-11-05

The Common Vulnerability Scoring System (CVSS) is FIRST's open standard for rating how severe a software vulnerability is, sometimes called the Common Vulnerability Score. An analyst encodes how the flaw is reached and what it breaks into a vector string, and a published formula turns that vector into a 0.0 to 10.0 score. CVSS measures worst-case severity, not your risk.

That last sentence is the one most teams get wrong. A CVSS score answers "how bad could this be, for a reasonable worst-case deployment?" It does not answer "will anyone exploit this?" or "does my code even call the vulnerable function?" FIRST, which maintains the standard, says so directly: "CVSS Base (CVSS-B) scores are designed to measure the severity of a vulnerability and should not be used alone to assess risk" ([CVSS v4.0 user guide](https://www.first.org/cvss/v4.0/user-guide)). FIRST's own data shows the size of the gap. Fixing every [CVE](https://socket.dev/glossary/common-vulnerabilities-and-exposures-cve) scored 7.0 or higher means working on 57.4% of all CVEs, and only 3.96% of those show observed exploitation ([FIRST EPSS model](https://www.first.org/epss/model)). Our position is that CVSS should set how fast you respond once you know a vulnerability matters. Exploitation evidence and [reachability](https://socket.dev/glossary/reachability-analysis) should decide whether it matters at all.

## How does CVSS turn a vector string into a score?

CVSS turns a vector string into a score by mapping each metric value to a fixed weight and combining the weights with a published equation. Here is a real one. We installed `minimist@1.2.5` into an empty project and ran `npm audit --json` (npm 10.9.9, November 5, 2026):

```json
{
  "name": "minimist",
  "severity": "critical",
  "via": [{
    "title": "Prototype Pollution in minimist",
    "url": "https://github.com/advisories/GHSA-xvch-5gv4-984h",
    "cwe": ["CWE-1321"],
    "cvss": {
      "score": 9.8,
      "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
    },
    "range": ">=1.0.0 <1.2.6"
  }]
}
```

The vector string is the actual data. The 9.8 is derived from it. Each `key:value` pair is one metric:

- `AV:N` Attack Vector is Network: the attacker can reach the flaw remotely.
- `AC:L` Attack Complexity is Low: no special conditions are needed.
- `PR:N` Privileges Required is None.
- `UI:N` User Interaction is None.
- `S:U` Scope is Unchanged: the impact stays inside the vulnerable component.
- `C:H/I:H/A:H` Confidentiality, Integrity, and Availability impact are all High.

The [CVSS v3.1 specification](https://www.first.org/cvss/v3.1/specification-document) assigns each value a weight and combines them in two halves. The impact half starts from an Impact Sub-Score, `ISS = 1 − (1−C)(1−I)(1−A)`, then scales it by 6.42 when scope is unchanged. The exploitability half is `8.22 × AV × AC × PR × UI`. The base score is the sum, capped at 10 and rounded up to one decimal.

> How the minimist CVSS 3.1 vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H becomes a base score of 9.8: impact 5.8731 plus exploitability 3.8870 equals 9.7602, rounded up to 9.8, in the Critical band

For the minimist vector, every input is at or near its maximum. High impact on all three properties gives an ISS of 0.9148 and an impact of 5.8731. Network, low complexity, no privileges, and no interaction carry weights of 0.85, 0.77, 0.85, and 0.85, which give an exploitability of 3.8870. Each High impact carries a weight of 0.56. The sum is 9.7602, which rounds up to 9.8, Critical.

You can reproduce every number in this article. The weights come straight from section 7.4 of the specification:

```python
import math

W = {"AV": {"N": .85, "A": .62, "L": .55, "P": .2}, "AC": {"L": .77, "H": .44},
     "UI": {"N": .85, "R": .62}, "CIA": {"H": .56, "L": .22, "N": 0}}
PR = {"U": {"N": .85, "L": .62, "H": .27}, "C": {"N": .85, "L": .68, "H": .5}}

def roundup(x):  # spec Appendix A: avoids floating-point drift
    i = round(x * 100000)
    return i / 100000 if i % 10000 == 0 else (math.floor(i / 10000) + 1) / 10

def base(vector):
    m = dict(p.split(":") for p in vector.split("/")[1:])
    iss = 1 - (1 - W["CIA"][m["C"]]) * (1 - W["CIA"][m["I"]]) * (1 - W["CIA"][m["A"]])
    changed = m["S"] == "C"
    impact = 7.52 * (iss - .029) - 3.25 * (iss - .02) ** 15 if changed else 6.42 * iss
    expl = 8.22 * W["AV"][m["AV"]] * W["AC"][m["AC"]] * PR[m["S"]][m["PR"]] * W["UI"][m["UI"]]
    if impact <= 0:
        return 0.0
    return roundup(min((1.08 if changed else 1) * (impact + expl), 10))

print(base("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"))  # 9.8  minimist
print(base("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H"))  # 10.0 Log4Shell
```

Changing one letter moves the score a long way. Flip `S:U` to `S:C`, meaning the exploit escapes the vulnerable component, and the same vector becomes 10.0. That is the score the [NVD](https://socket.dev/glossary/national-vulnerability-database-nvd) assigned to Log4Shell, [CVE-2021-44228](https://nvd.nist.gov/vuln/detail/CVE-2021-44228). The score then maps onto five qualitative bands: None (0.0), Low (0.1 to 3.9), Medium (4.0 to 6.9), High (7.0 to 8.9), and Critical (9.0 to 10.0). Those labels are what `npm audit`, the [GitHub Advisory Database](https://socket.dev/glossary/github-advisory-database), and most scanners print.

## Why does minimist score 9.8?

minimist scores 9.8 because CVSS tells library analysts to assume the worst plausible caller. minimist parses command-line arguments. To pollute `Object.prototype` through it, an attacker has to control the strings passed to the parser. In most programs those come from whoever typed the command. The vector still says `AV:N`, network-reachable, with no privileges and no user interaction.

That is not a scoring mistake. It is the rule. The [CVSS v3.1 user guide](https://www.first.org/cvss/v3.1/user-guide) is explicit about libraries: "The analyst should score for the reasonable worst-case implementation scenario." A library has no single deployment, so its score describes the most exposed program anyone might build with it. For open source dependencies, this one rule explains most of the distance between a scanner's severity column and what a team actually needs to fix.

## CVSS v4.0 replaced the equation with expert-ranked lookup tables

CVSS v4.0, published by FIRST on [November 1, 2023](https://www.first.org/cvss/v4.0/user-guide), dropped the weighted equation entirely. Its [specification](https://www.first.org/cvss/v4.0/specification-document) describes how the working group grouped about 15 million possible vectors into 270 equivalence sets. Experts ranked representative vectors from each set against each other, and that ranking became a lookup table. A vector's score is its set's table value, adjusted by interpolation toward neighboring sets and rounded to one decimal.

The metric model changed too. Scope is gone, replaced by separate impact metrics for the vulnerable system (`VC/VI/VA`) and subsequent systems (`SC/SI/SA`). A new Attack Requirements (`AT`) metric captures preconditions such as a race window. User Interaction now distinguishes Passive from Active. The Temporal group became the Threat group, and an optional Supplemental group carries non-scoring context such as Automatable and Safety.

v4.0 also forces you to say which metric groups were used. CVSS-B is Base only, CVSS-BT adds Threat, CVSS-BE adds Environmental, and CVSS-BTE has all three. The Threat group holds one metric, Exploit Maturity (`E`), and the spec defines its unset value as equivalent to Attacked. In other words, a Base-only v4.0 score assumes active exploitation.

That default is easy to see in the numbers. We scored Log4Shell-shaped vectors with the [`cvss`](https://pypi.org/project/cvss/) Python package (version 3.6), which implements the v4.0 reference lookup:

| CVSS v4.0 vector, after the shared prefix CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H | Score |
| --- | --- |
| SC:H/SI:H/SA:H (Base only, so E defaults to Attacked) | 10.0 Critical |
| SC:H/SI:H/SA:H/E:P (public proof of concept) | 9.3 Critical |
| SC:H/SI:H/SA:H/E:U (no known exploit) | 9.1 Critical |
| SC:N/SI:N/SA:N (Base only) | 9.3 Critical |
| SC:N/SI:N/SA:N/E:U (no known exploit) | 8.1 High |

Adding threat information can only lower a v4.0 score, never raise it. The worst-case assumption is now written into the spec instead of hidden in practice. When you consume a v4.0 score, check whether it carries an `E` value. If it does not, it already assumes the bug is under attack.

|  | CVSS v3.1 | CVSS v4.0 |
| --- | --- | --- |
| Score computation | Weighted equation | Expert-ranked lookup table plus interpolation |
| Cross-component impact | S:U or S:C (Scope) | Separate VC/VI/VA and SC/SI/SA metrics |
| Preconditions | Folded into AC | Separate AT (Attack Requirements) |
| Time-varying metrics | Temporal: E, RL, RC | Threat: E only |
| Unset exploit maturity | Treated as worst case | Treated as Attacked (worst case) |
| Labeling | "CVSS score" | CVSS-B, -BT, -BE, -BTE |
| Verdict | Still what every advisory in this article carries; fine to consume | Better model; prefer it when a source offers both, and always check which groups were scored |

## Why does the same CVE get different CVSS scores?

The same CVE gets different CVSS scores because several organizations score it independently, and CVSS leaves room for judgment. The CVE Numbering Authority (CNA) that assigns the CVE may include a score. The NVD has historically added its own. CISA adds scores through its Vulnrichment program as an Authorized Data Publisher (ADP). The GitHub Advisory Database scores the advisories it reviews.

Those scores disagree more than most dashboards admit. Jerry Gamblin's [Consensus Engine](https://socket.dev/blog/nist-officially-stops-enriching-most-cves) compared the two sources for 1,567 CVEs that both NVD and GitHub had scored. NVD came in higher 885 times, and GitHub came in higher 682 times. The largest single gap was 6.9 points. Gaps of 4 points or more appeared repeatedly, which is the difference between Medium and Critical for the same bug.

The curl project's [CVE-2024-11053](https://socket.dev/blog/curl-project-and-go-security-teams-reject-cvss-as-broken) shows how an ADP score can override the people who know the code. curl rated the netrc credential leak Low. Through its [Vulnrichment](https://socket.dev/blog/cisa-launches-vulnrichment-project-as-nvd-backlog-hits-10-000) program, CISA published a 9.1 Critical. After curl's maintainer, Daniel Stenberg, objected, CISA revised it to 3.4. We read the current record from the CVE.org API on November 5, 2026:

```json
{
  "cna": { "providerMetadata": { "shortName": "curl" },
           "title": "netrc and redirect credential leak" },
  "adp": [{
    "providerMetadata": { "shortName": "CISA-ADP" },
    "metrics": [{ "cvssV3_1": {
      "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:L/I:N/A:N",
      "baseScore": 3.4,
      "baseSeverity": "LOW"
    }}]
  }]
}
```

Run that vector through `cvss31.py` and you get 3.4. A 9.1 under v3.1 needs something like `AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N`. The disagreement was never about arithmetic. It was about which letters an analyst picked. The incident prompted Stenberg's ["CVSS is dead to us"](https://daniel.haxx.se/blog/2025/01/23/cvss-is-dead-to-us/) post, and the Go security team has also declined to use CVSS. curl's [advisory](https://curl.se/docs/CVE-2024-11053.html) still says "Severity: Low," and the project no longer puts CVSS on its own CVE records.

The pipeline that supplies most CVSS scores is also thinning out. [The NVD halted most enrichment](https://socket.dev/blog/nvd-halts-cve-enrichment) in February 2024. In April 2026, [NIST stopped enriching most CVEs](https://socket.dev/blog/nist-officially-stops-enriching-most-cves). As of November 2026, the NVD prioritizes CVEs in CISA's [KEV catalog](https://socket.dev/glossary/cisa-kev), software the federal government uses, and software designated critical. NIST also said the NVD would stop producing its own CVSS score when the CNA already supplied one. NIST attributed the change to CVE submissions rising 263% between 2020 and 2025. Meanwhile [2025 set a record of 48,185 CVEs](https://socket.dev/blog/cve-volume-surges-past-48k-in-2025), with an average CVSS of 6.60. Any process that treats "the CVSS score" as a single authoritative number is reading one of several opinions, and increasingly the vendor's own.

## What is the difference between CVSS and EPSS?

CVSS measures how bad a vulnerability would be if exploited, and the Exploit Prediction Scoring System ([EPSS](https://socket.dev/glossary/exploit-prediction-scoring-systems-epss)) estimates how likely exploitation is. FIRST maintains both. EPSS is a machine-learning model that publishes a daily probability, from 0 to 1, that exploitation activity will be observed against a CVE in the next 30 days. It is trained on exploitation telemetry from honeypots and intrusion sensors, not on analyst judgment.

The two disagree constantly, and both of our running examples show it. On November 5, 2026, the [FIRST EPSS API](https://api.first.org/data/v1/epss?cve=CVE-2021-44228,CVE-2021-44906) returned 0.99999 for Log4Shell and 0.04581 for minimist's CVE-2021-44906. Both are CVSS Critical. One is near-certain to be exploited in any 30-day window. The other sits around a 4.6% chance.

FIRST measured what each approach buys on October 2023 data covering 139,473 CVEs. The rule "remediate everything CVSS 7 or above" and the rule "remediate everything EPSS 0.1 or above" produce very different workloads:

> Comparison of remediation strategies on FIRST's October 2023 data. CVSS 7 and above: effort 57.4 percent, coverage 82.2 percent, efficiency 3.96 percent. EPSS 0.1 and above: effort 2.7 percent, coverage 63.2 percent, efficiency 65.2 percent.

| Strategy | Share of all CVEs you work on (effort) | Share of exploited CVEs you catch (coverage) | Share of your work that was exploited (efficiency) |
| --- | --- | --- | --- |
| CVSS 7.0 and above | 57.4% | 82.2% | 3.96% |
| EPSS 0.1 and above | 2.7% | 63.2% | 65.2% |

The CVSS rule catches more exploited CVEs, at roughly 21 times the effort, and about 96% of that effort goes to CVEs with no observed exploitation. FIRST notes the 0.1 threshold is arbitrary, not a recommendation. Neither rule is a complete strategy. They answer different questions. The right move is to combine them with CISA's KEV list, a decision framework such as [SSVC](https://socket.dev/glossary/stakeholder-specific-vulnerability-categorization), and signals about your own code.

| Signal | Question it answers | Source | Blind spot |
| --- | --- | --- | --- |
| CVSS | How bad, in the worst plausible deployment? | CNA, NVD, CISA ADP, GHSA analysts | Ignores likelihood and your deployment |
| EPSS | How likely is exploitation in the next 30 days? | FIRST model, daily | Says nothing about impact or about your code |
| CISA KEV | Has this been exploited in the wild? | CISA | Conservative and lagging; absence proves nothing |
| SSVC | What should this organization do: track, attend, or act? | Decision tree run by each organization | Only as good as the inputs you feed it |
| Reachability | Can my code reach the vulnerable function? | Static analysis of your dependency graph and code | Says nothing about attacker interest |

Verdict: use KEV and EPSS to decide what is urgent, reachability to decide what is relevant, and CVSS to set the deadline once both say yes. Use CVSS alone only when a compliance framework requires it, and record the vector, not just the number.

## CVSS has nothing to say about malicious packages

CVSS scores vulnerabilities: mistakes in otherwise legitimate code. A [malicious package](https://socket.dev/glossary/malicious-package) is not a mistake. A typosquat or a hijacked release that runs a credential stealer in a `postinstall` script has no vulnerable function, no affected-but-patched range, and usually no CVE. The npm security team and the OpenSSF Malicious Packages project report these in OSV format, and GitHub publishes them as a separate class of [malware advisories](https://docs.github.com/en/code-security/security-advisories/working-with-global-security-advisories-from-the-github-advisory-database/about-the-github-advisory-database).

A pipeline that gates on "no Critical or High CVSS" therefore passes most supply chain attacks without comment. That is why, at Socket, malware is a different alert class from vulnerabilities. Our CVE alerts come in four severities ([Critical CVE, High CVE, Medium CVE, Low CVE](https://socket.dev/alerts)). "Known malware" is a separate alert. It fires when Socket's scanner, our threat researchers, or an external database flag the package version as malicious, and no severity score is involved.

## Severity should set the clock, not pick the work

The workable use of CVSS is narrow. Use the band to set response time limits for vulnerabilities you have already decided are real. Do not use it to decide which of 2,000 findings deserve a ticket. Our [guide to building a vulnerability management program](https://socket.dev/blog/creating-an-effective-vulnerability-management-program-for-open-source-vulnerabilities) starts with CVSS-tiered SLAs, such as 7 days for Critical, because they are simple and auditors understand them. It also says what breaks first as volume grows. Developers hit vulnerability fatigue, SLAs slip, and automated prioritization becomes essential.

For open source dependencies, that prioritization is reachability analysis. Most CVEs in a transitive dependency sit in code your application never executes. Socket's [precomputed reachability](https://socket.dev/blog/announcing-precomputed-reachability-analysis-in-socket) works from manifests alone and typically marks about 60% of CVEs unreachable. [Full application reachability](https://socket.dev/blog/introducing-tier-1-reachability) analyzes your code too and typically filters about 80%. By default, Socket downgrades unreachable CVEs to a "monitor" action instead of "block" or "warn," so a 9.8 that your code cannot reach stops failing builds. The cost is that reachability is a static approximation. It cannot tell you whether attackers care, and paths the analysis cannot resolve come back "unknown" or "maybe reachable" rather than cleared.

The harder problem is upstream of any tool. As Stenberg put it, CVSS works when you know exactly how the product is used. Open source is the opposite case. One library ships into millions of deployments, the rules require scoring the worst one, and the organizations that used to provide an independent second opinion are pulling back. A CVSS score on an open source package is best read as an upper bound written by someone who has never seen your code. Treat it that way, keep the vector next to the number, and let evidence about exploitation and reachability move it down.
