Safeguard
Vulnerability Analysis

When One CVE Has Three Scores, Taking the Highest Is Not Caution

NVD says 9.9. The vendor says 7.0. CVSS v4 says 6.3. Collapsing that to 9.9 does not make you conservative — it discards the disagreement, which was the most informative thing you had.

Aman Khan
Security Analyst
5 min read

Pull the scores for a single CVE from the sources your pipeline already consumes and you will regularly find something like this:

SourceScoreVersion
NVD9.9CVSS v3.1
CNA (the vendor)7.0CVSS v3.1
CVSS v46.3CVSS v4.0

Most tooling shows 9.9. The reasoning, when anyone states it, is that taking the maximum is the conservative choice.

It is not conservative. It is lossy. A 2.9-point spread between the vendor who wrote the code and the national database is a signal about the vulnerability, and showing one number deletes it.

Why the sources disagree

The disagreements are not noise, and they are not usually errors. They come from three structural causes.

Different assumed deployments. CVSS base scores are supposed to describe the vulnerability independent of environment, but "independent of environment" still requires assuming something about how the software is typically run. A CNA that knows the library is nearly always used server-side scores differently from an analyst assuming the worst plausible deployment.

Different readings of scope and privilege. The Scope metric in CVSS v3 is the single largest source of disagreement in practice. Whether a flaw "changes scope" is a judgement about security-authority boundaries, and reasonable analysts differ. Scope change alone can swing a score by two points or more.

Genuine version differences. CVSS v4 is not a re-scoring of v3 on the same scale. It restructured the metric groups, added Attack Requirements, split user interaction, and introduced supplemental metrics like Automatable and Recovery. A v4 score is a different measurement, not a corrected one, and comparing v4 to v3.1 as though they were the same number is a category error.

That last point is worth dwelling on: a v4 score being lower than a v3 score for the same CVE tells you very little on its own. The scales are not calibrated against each other.

What the maximum actually does

Three concrete harms, in increasing order of expense.

It makes Critical meaningless. If the display always takes the highest available number, and NVD's methodology is systematically more pessimistic, then the count of Criticals in your estate is largely a function of which source you happened to ingest. Teams learn this fast, and the response is to stop treating Critical as a signal — which is exactly the outcome the severity system exists to prevent.

It hides the vendor's own view. The CNA is usually the party that knows the code best. When they score a flaw materially lower than the aggregator, that is a data point worth reading — sometimes because they are minimising, and sometimes because they know the vulnerable path requires a configuration almost nobody uses. Either way you want to see it, and a maximum-only display guarantees you never will.

It breaks the audit trail. "Why is this Critical?" should have an answer better than "because one of our sources said so and we do not record which". If a remediation was prioritised on a 9.9 that the vendor scored 7.0, someone will eventually ask, and "the tool showed 9.9" is not a defence.

Keep them distinct in the data model

The fix is a schema change, not a scoring change. Store scores as a list of attributed measurements rather than a single field:

"scores": [
  { "source": "nvd",     "value": 9.9, "vector": "CVSS:3.1/AV:N/AC:L/..." },
  { "source": "cna",     "value": 7.0, "vector": "CVSS:3.1/AV:N/AC:H/..." },
  { "source": "cvss_v4", "value": 6.3, "vector": "CVSS:4.0/AV:N/AC:L/AT:P/..." }
]

Then pick a primary for sorting and display, by an explicit documented rule — prefer the CNA where present, fall back to NVD, and never mix v4 with v3 in the same ordering — and surface disagreement as its own attribute:

7.0 (vendor) · sources disagree by 2.9 points

That one line does more triage work than the number beside it. It tells the reader the score is contested before they build a sprint around it.

Score disagreement is a triage input

Once disagreement is a first-class field, it becomes usable rather than merely honest.

A finding where every source agrees on 9.8 and a finding where sources span 6.3 to 9.9 warrant different treatment. The first is unambiguous and should be actioned on the number. The second needs somebody to read the vector strings and decide which assumed deployment matches yours — a fifteen-minute job that a maximum-only display never prompts anyone to do.

And the vectors are what make that possible, which is why storing them matters as much as storing the values. Two sources landing on 7.0 by entirely different routes is a different situation from two sources agreeing on the metrics. Only the vector shows you which you have.

What to pair it with

Severity is not exposure. A contested 9.9 in a package your application imports but never calls, on a code path requiring a configuration you do not run, is a lower priority than an uncontested 6.5 in your authentication flow.

The scores tell you how bad the flaw is in the abstract. Reachability tells you whether your code touches it. Environmental preconditions tell you whether the conditions the advisory requires hold in your deployment. All three are inputs, none of them is the answer, and a queue sorted on any one of them alone will spend its first week on the wrong work.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.