GitHub
ESC

Scoring & Severity

Base score

Every Vector exposes a base_score : Float64 and a severity : CVSS::Severity:

v = CVSS.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
v.base_score  # => 9.8
v.severity    # => CVSS::Severity::Critical

The qualitative Severity enum is unified across versions:

Score Severity
0.0 None
0.1 – 3.9 Low
4.0 – 6.9 Medium
7.0 – 8.9 High
9.0 – 10.0 Critical

CVSS v2.0 has no Critical band — v2.severity returns at most High.

Temporal & Environmental scores (v2 / v3)

v = CVSS::V3::Vector.parse(
  "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C"
)
v.temporal_score       # => 9.1
v.environmental_score  # => 9.1  (no env metrics → falls back to temporal)

When environmental metrics are set, the modifier weights and scope-aware Impact formula kick in:

v = CVSS::V3::Vector.parse(
  "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:R" \
  "/CR:H/IR:H/AR:M/MAV:A/MAC:H/MPR:N/MUI:N/MS:U/MC:H/MI:N/MA:N"
)
v.base_score           # => 9.8
v.environmental_score  # => 6.3

CVSS v3.0 and v3.1 share the same RoundUp at the base level, but use different polynomials in the modified impact formula. The library handles both transparently.

CVSS v4.0 — single score

CVSS v4.0 folds Threat (E) and Environmental metrics directly into the macro-vector lookup, so there is one combined score rather than a base / temporal / environmental triple:

v = CVSS.parse(
  "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"
)
v.base_score   # => 9.3

You can read the 6-character macro vector that drives the lookup:

v.macro_vector  # => "000200"

Sub-scores (v3.x)

For tooling and debugging, v3 vectors expose the intermediate ISS, Impact, and Exploitability sub-scores defined in the spec:

v = CVSS::V3::Vector.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
v.iss                       # => 0.9148...
v.impact_subscore           # => 5.873...
v.exploitability_subscore   # => 3.887...

Severity from arbitrary scores

CVSS::Severity.from_score(7.5)     # => CVSS::Severity::High
CVSS::Severity.from_v2_score(3.9)  # => CVSS::Severity::Low (CVSS v2 banding)

Severity is a normal Crystal enum, so it gets <=>, predicate methods (vec.severity.critical?), and string conversion (vec.severity.to_s) for free.