Skip to content
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after any top-up. Until Nov 30.

How it works

CVSS Explained: Reading and Scoring a Vector String

What a CVSS score like 9.8 means, how v4.0 and v3.1 vector strings are built, and how to decode and recalculate one by hand.

Beginner12 min readBy NotHoly

CVSS is the scoring system behind most published vulnerability scores. You will see a number between 0.0 and 10.0, a severity word such as "Critical", and a long string that looks like a serial number. This article explains what each part means, how to read the string field by field, and how to check a published score yourself.

What CVSS is

CVSS stands for Common Vulnerability Scoring System. FIRST, the Forum of Incident Response and Security Teams, maintains it. The current version is 4.0, published in late 2023. Version 3.1 still appears on most older advisories and in the NVD database, so you need to read both.

CVSS gives a flaw a score from 0.0 to 10.0. The score comes from the properties of the flaw: how hard it is to reach, what the attacker needs first, and how much damage the flaw can do if someone uses it. Most tools map the number to a rating:

Rating Score range
None 0.0
Low 0.1 to 3.9
Medium 4.0 to 6.9
High 7.0 to 8.9
Critical 9.0 to 10.0

The ratings are the same in v3.1 and v4.0. The math behind the number is different.

Why it matters

Teams need a shared word for severity. A vendor writes an advisory, a scanner flags an asset, a manager asks which patch goes first, and everyone should mean the same thing by "high". CVSS gives them a common scale. The vector string also records the reasoning, so a reader can check whether the score fits their own system.

The score has limits, and most mistakes come from ignoring them. The section on severity and likelihood covers the main one.

The four metric groups

CVSS v4.0 organizes its metrics into four groups. Only the base group is needed to produce a score.

Base

Base metrics describe the flaw as it exists in the vulnerable software, without knowledge of your network. Advisories almost always publish them. In v4.0 they cover:

  • Exploitability: attack vector (AV), attack complexity (AC), attack requirements (AT), privileges required (PR) and user interaction (UI).
  • Impact on the vulnerable system: confidentiality (VC), integrity (VI) and availability (VA).
  • Impact on subsequent systems: confidentiality (SC), integrity (SI) and availability (SA). These describe damage to other systems the attacker can reach after the first one falls.

Threat

Threat metrics change over time. The v4.0 threat metric is E, for exploit maturity. It can be A (attacked, meaning there is evidence of exploitation), P (proof of concept exists), U (unreported) or X (not defined, the default). A score with E set can differ from the base score, and it moves when the threat picture changes.

Environmental

Environmental metrics describe your own deployment. Requirement metrics (CR, IR and AR for confidentiality, integrity and availability) say how much each property matters to you. Modified base metrics let you override a base value when your configuration changes it. A flaw on an internet-facing login page and the same flaw in a lab VM with no network route can share a base score and still deserve different priorities in your environment.

Supplemental

Supplemental metrics add context such as safety impact, automatability, recovery effort or urgency. They do not change the score. They help a team sort work.

Version 3.1 uses the Base, Temporal and Environmental groups. It has no supplemental group, and its temporal metrics (exploit code maturity, remediation level and report confidence) look different from the single E metric in v4.0.

How to read a vector string

The vector string is the compact form of the metrics. It starts with the version, then lists each metric as Name:Value, separated by slashes. Every value is a single letter. Once you know the letters, you can read any string without a table.

A v4.0 example

text
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

Decode it field by field:

Field Value Meaning
CVSS 4.0 Specification version
AV N Attack vector: network. The other values are A (adjacent), L (local) and P (physical).
AC L Attack complexity: low. The other value is H.
AT N Attack requirements: none. The other value is P (present), for conditions such as a race window or a particular deployment setting.
PR N Privileges required: none. The others are L (low) and H (high).
UI N User interaction: none. The others are P (passive) and A (active).
VC H Confidentiality impact on the vulnerable system: high. The others are L and N.
VI H Integrity impact on the vulnerable system: high.
VA H Availability impact on the vulnerable system: high.
SC N Confidentiality impact on subsequent systems: none. The others are L and H.
SI N Integrity impact on subsequent systems: none.
SA N Availability impact on subsequent systems: none.

In plain words: someone on the network can exploit this without logging in, without a special setup and without a victim doing anything. The attack gives full control over the confidentiality, integrity and availability of the vulnerable component, and nothing beyond it. FIRST's calculator returns a base score of 9.3 (Critical) for this vector. The lab below shows how to check that.

A v3.1 example

text
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

The v3.1 fields are similar, with three differences. There is no AT field. The impact fields are C, I and A, with no split between the vulnerable and subsequent systems. And S (scope) appears instead:

Field Value Meaning
AV N Attack vector: network. The others are A, L and P.
AC L Attack complexity: low. The other value is H.
PR N Privileges required: none. The others are L and H.
UI N User interaction: none. The other value is R (required).
S C Scope changed: the exploit can affect a component beyond the one that holds the flaw. U means unchanged.
C H Confidentiality: high. The others are L and N.
I H Integrity: high.
A H Availability: high.

The S field explains most of the gap between a 9.8 and a 10.0 later in this article.

How v4.0 differs from v3.1

The changes are structural, so reading a score from one version as if it came from the other causes errors.

  • Scope is gone. v3.1 asked whether the impact crossed a security boundary. v4.0 splits the impact into the vulnerable system and the subsequent systems, so the same question is answered with direct fields.
  • Attack requirements were added. AT captures conditions an attacker must meet that are not privileges, such as a particular configuration or a race window.
  • User interaction has more values. v4.0 separates passive interaction (a victim only views or renders something) from active interaction (a victim must click or act).
  • The threat group is simpler. One metric, E, replaces the three temporal metrics of v3.1.
  • Supplemental metrics are new. They carry context without changing the score.
  • The score is looked up, not just calculated. v3.1 uses a short arithmetic formula you can follow on a page. v4.0 maps the base metrics to a lookup table and interpolates within it, as its specification describes. Use the official calculator or a tested library for v4.0, not a spreadsheet you wrote yourself.

Advisories still mix versions. Some publish both a v3.1 and a v4.0 score for the same flaw, and the two numbers can differ. Check the prefix before you compare two numbers.

Severity is not likelihood

This is the point that matters most, and the one people skip. CVSS measures how severe a flaw is if it is exploited. It does not tell you whether anyone will exploit it.

A flaw with a Critical base score can sit unexploited for years. A Medium flaw can be under active attack this week. CVSS does not see that. The threat group can carry some exploitation information (E set to A, for example), but someone still has to set it and keep it current. Other sources answer the likelihood question:

  • EPSS estimates the probability that a flaw will be exploited in the next 30 days, based on observed data.
  • The CISA Known Exploited Vulnerabilities catalog lists flaws with evidence of exploitation in the wild.

If you need to decide what to patch first, read severity, likelihood and exposure together. A high CVSS score on a system nobody can reach is usually less urgent than a medium score on a public login page with exploitation reported.

For the basics of what a vulnerability is and how it gets an identifier, read what is a vulnerability and what is a CVE identifier.

Hands-on: decode a published vector and recalculate it

You need a terminal and a browser. The lab uses public data only. It touches the public NVD API and the FIRST website, and nothing else.

Step 1: pull the vector from NVD

The NVD API returns the scores that NVD holds for a CVE. For CVE-2021-44228, the Log4Shell flaw in Apache Log4j, run:

bash
curl -s 'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2021-44228' | jq -r '.vulnerabilities[0].cve.metrics | .. | .vectorString? // empty'

The filter walks the whole metrics object and prints every vectorString it finds. On the machine where this article was checked, jq was not installed. The same walk ran with Node 24 instead. Use this fallback if you do not have jq:

bash
curl -s 'https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2021-44228' -o nvd_44228.json
node -e "const j=JSON.parse(require('fs').readFileSync('nvd_44228.json','utf8'));const out=[];(function walk(n){if(n&&typeof n==='object'){for(const[k,v]of Object.entries(n)){if(k==='vectorString')out.push(v);else walk(v);}}})(j.vulnerabilities[0].cve.metrics);console.log(out.join('\n'));"

Output observed on 8 November 2026:

text
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
AV:N/AC:M/Au:N/C:C/I:C/A:C

The first two lines are the v3.1 vector from NVD's own record and from a second record NVD lists. The third line is a CVSS v2.0 vector, an older scale. Ignore it for this lab. NVD can update a record, so your output may differ from this one.

Step 2: decode it on paper

Take the first line and fill in a table like the ones above. Write down every field and its meaning before you open a calculator. Two questions help:

  1. What must the attacker already have? PR:N and UI:N say that nothing is required.
  2. Which property is lost? C, I and A are all H, which means full loss of each.

The field that deserves a second look is S:C. It means the exploit reaches beyond the component that holds the flaw. That is why this vector scores higher than the same vector with S:U.

Step 3: enter it in the calculator

Open the calculator for the version in your string. For v3.1, use https://www.first.org/cvss/calculator/3.1. For v4.0, use https://www.first.org/cvss/calculator/4.0. You can paste the vector after the # in the URL to pre-fill the form.

For the v4.0 example from earlier, open this link:

text
https://www.first.org/cvss/calculator/4.0#CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N

The calculator should return a base score of 9.3 (Critical) for this vector. I did not run it in a browser while writing this, so treat 9.3 as the value to confirm. If it does not, compare each letter in the form with your decoded table.

For the v3.1 vector from NVD, open the v3.1 calculator and set each value to match your table. The expected score is 10.0. If the form stays empty after the page loads, select the values by hand, which is a useful check on its own.

Step 4: recalculate v3.1 with a script

For v3.1 the base formula is short enough to fit in a few lines. This script follows the v3.1 specification. It is for checking your understanding, not for production use. Save it as cvss31.js:

js
// CVSS v3.1 base score, following the FIRST v3.1 specification.
function roundup(x) {
  const i = Math.round(x * 100000);
  return i % 10000 === 0 ? i / 100000 : (Math.floor(i / 10000) + 1) / 10;
}
function base(vec) {
  const m = Object.fromEntries(vec.split('/').slice(1).map(p => p.split(':')));
  const S = m.S === 'C';
  const AV = { N: 0.85, A: 0.62, L: 0.55, P: 0.2 }[m.AV];
  const AC = { L: 0.77, H: 0.44 }[m.AC];
  const PR = { N: 0.85, L: S ? 0.68 : 0.62, H: S ? 0.5 : 0.27 }[m.PR];
  const UI = { N: 0.85, R: 0.62 }[m.UI];
  const cia = { H: 0.56, L: 0.22, N: 0 };
  const iss = 1 - (1 - cia[m.C]) * (1 - cia[m.I]) * (1 - cia[m.A]);
  const imp = S ? 7.52 * (iss - 0.029) - 3.25 * Math.pow(iss - 0.02, 15) : 6.42 * iss;
  const expl = 8.22 * AV * AC * PR * UI;
  if (imp <= 0) return 0;
  return S ? roundup(Math.min(1.08 * (imp + expl), 10)) : roundup(Math.min(imp + expl, 10));
}
for (const v of process.argv.slice(2)) console.log(v, '->', base(v).toFixed(1));

Run it on the NVD vector, and on the same vector with the scope set to unchanged:

bash
node cvss31.js "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H" "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"

Output observed on 8 November 2026:

text
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H -> 10.0
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H -> 9.8

The first line matches the NVD score of 10.0. The second line gives the 9.8 that many people remember for this flaw. The only change is S:U. When you see a score, read its vector before you trust the number.

What to look for

If your decoded table, the calculator and the script agree, you can read a CVSS string. If they disagree, find the field that differs. The usual causes are a wrong letter (AV:L means local, not low), mixing up C, I and A, or comparing a v3.1 score with a v4.0 score as if both came from the same scale.

Common mistakes

  • Treating the score as risk. A 10.0 that nobody can reach is a different problem from a 10.0 on your internet-facing server. Add exposure and exploitation data.
  • Comparing scores across versions. The v3.1 and v4.0 numbers come from different math. Compare like with like.
  • Stopping at the headline number. The vector says why the score is what it is. Read it.
  • Trusting a score without its vector. A bare number cannot be checked. Pull the vector from NVD or the vendor.
  • Editing a score by hand. If you change one letter, recalculate. A typed number that does not match its vector is an error to report.
  • Assuming the default describes your environment. The base score assumes nothing about your setup. Environmental metrics exist for that reason.

How to go further

Read the base metric definitions in the specification first. They are short, and the exact wording of each value is where the answers live. Then decode five or six published vectors on paper before you check each one in the calculator. Pick a few with S:C or AT:P and see how the score moves.

After that, read about the exploitation side: what EPSS estimates and what the KEV catalog records. Those two answer the question CVSS leaves open, which is whether anyone is attacking this flaw now.

Sources

Sources

  1. FIRST, CVSS v4.0 Specification Document
  2. FIRST, CVSS v3.1 Specification Document
  3. FIRST, CVSS v4.0 Calculator
  4. NIST, NVD CVSS information
  5. NIST, NVD CVE API 2.0

More in Vulnerability intelligence