cvss

A Crystal implementation of the Common Vulnerability Scoring System (CVSS) — parsing, scoring, and serialization for vector strings.

Supported versions:

  • CVSS v2.0
  • CVSS v3.0 / v3.1
  • CVSS v4.0 (full macro-vector lookup; algorithm ported from FIRST's reference calculator)

Installation

Add the dependency to your shard.yml:

dependencies:
  cvss:
    github: hahwul/cvss.cr

Then shards install.

Usage

Auto-detecting the version

CVSS.parse inspects the CVSS:x.y/ prefix and dispatches to the appropriate version-specific parser. Vector strings without a prefix are treated as CVSS v2.0.

require "cvss"

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

CVSS.parse("AV:N/AC:L/Au:N/C:P/I:P/A:P").base_score
# => 7.5

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").base_score
# => 9.3

Working with a specific version

You can also use the version-specific classes directly when you need access to typed metric values, temporal scores, or modified-base overrides.

v3 = 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")
v3.base_score          # => 9.8
v3.temporal_score      # => 9.1
v3.environmental_score # => 9.1
v3.av                  # => CVSS::V3::AttackVector::Network
v3.severity            # => CVSS::Severity::Critical

Building a vector programmatically

v = CVSS::V3::Vector.new(
  av: CVSS::V3::AttackVector::Network,
  ac: CVSS::V3::AttackComplexity::Low,
  pr: CVSS::V3::PrivilegesRequired::None,
  ui: CVSS::V3::UserInteraction::None,
  s:  CVSS::V3::Scope::Unchanged,
  c:  CVSS::V3::Impact::High,
  i:  CVSS::V3::Impact::High,
  a:  CVSS::V3::Impact::High,
)
v.to_s         # => "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
v.base_score   # => 9.8

Non-raising parse

CVSS.parse? returns nil instead of raising on malformed input or unsupported versions:

if vec = CVSS.parse?(user_input)
  # use vec
end

The same parse? method is also available on each version-specific class: CVSS::V3::Vector.parse?(input), CVSS::V4::Vector.parse?(input), etc.

Equality, hashing, and ordering

Vectors are value types — two parsed vectors are == when they represent the same CVSS string, and they hash consistently so they can be used as Hash keys or Set elements:

a = CVSS.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
b = CVSS.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H")
a == b      # => true
a.hash == b.hash # => true

Vectors are also Comparable by base_score, so sorting and min/max/</> all work — even across CVSS versions:

vulns = inputs.map { |s| CVSS.parse(s) }
vulns.sort.last  # most severe vulnerability

Cross-version == always returns false (a v3 vector and a v4 vector are never structurally equal even if their scores happen to match).

Sub-scores (CVSS v3.x)

For tooling and debugging you can read the intermediate ISS, Impact and Exploitability sub-scores:

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

JSON serialization

Vector#to_json produces a payload aligned with the FIRST CVSS JSON Schema and the NVD CVE feed format:

require "json"

vec = CVSS.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")
puts vec.to_json
# {
#   "version": "3.1",
#   "vectorString": "CVSS:3.1/...",
#   "baseScore": 9.8,
#   "baseSeverity": "CRITICAL",
#   "exploitabilityScore": 3.9,
#   "impactScore": 5.9,
#   "temporalScore": 9.1,
#   "temporalSeverity": "CRITICAL"
# }

CVSS.from_json reads either a flat object or an NVD-nested {"cvssData": {...}} payload, recomputing scores from the vectorString (it never trusts a baseScore field in the input):

CVSS.from_json(%({"vectorString": "CVSS:3.1/AV:N/..."})).base_score
CVSS.from_json(File.read("nvd_response.json"))

Classification helpers

Every Vector exposes predicate methods for the most common filtering queries — useful for triaging large vulnerability lists:

vec.network?                   # AV:N
vec.local?                     # AV:L
vec.physical?                  # AV:P (v3, v4)
vec.requires_privileges?       # PR != N (v3, v4)
vec.requires_authentication?   # Au != N (v2)
vec.requires_user_interaction? # UI != N
vec.scope_changed?             # S:C (v3 only)
vec.impacts_subsequent_system? # any of SC/SI/SA != N (v4 only)
vec.impacts_confidentiality?
vec.impacts_integrity?
vec.impacts_availability?

Hash export

Vector#to_h returns a Hash(String, String) of metric short-codes in canonical order. Optional metrics are omitted when not set.

CVSS.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F").to_h
# => {"AV" => "N", "AC" => "L", "PR" => "N", "UI" => "N",
#     "S" => "U", "C" => "H", "I" => "H", "A" => "H", "E" => "F"}

MacroVector and Nomenclature (CVSS v4.0)

v4 = CVSS::V4::Vector.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")
v4.macro_vector       # => "000200"
v4.nomenclature.to_s  # => "CVSS-B"

# Per CVSS v4.0 spec §6, the label reflects which optional metric groups
# carry meaningful (non-X) values:
bte = CVSS::V4::Vector.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/E:A/MAV:P"
)
bte.nomenclature.to_s         # => "CVSS-BTE"
bte.threat_set?               # => true
bte.environmental_set?        # => true

Errors

All exceptions inherit from CVSS::Error:

  • CVSS::ParseError — malformed vector string, missing required metrics, or duplicate metrics.
  • CVSS::InvalidMetricError — a metric carries a value outside its allowed set (e.g. AV:Q).
  • CVSS::UnknownVersionErrorCVSS:x.y/ prefix references a version this library does not implement.

Severity

CVSS::Severity is a unified enum (None, Low, Medium, High, Critical) used across all versions. CVSS v2 only defines Low/Medium/High, so its severity method maps 0.0 to None and never returns Critical.

Development

crystal spec

License

MIT. The CVSS v4.0 macro-vector lookup tables and scoring algorithm are ported from FIRSTdotorg/cvss-v4-calculator (BSD-2-Clause, Copyright FIRST, Red Hat, and contributors).

Contributors

  • hahwul — creator and maintainer
Repository

cvss.cr

Owner
Statistic
  • 2
  • 0
  • 0
  • 0
  • 0
  • about 11 hours ago
  • May 1, 2026
License

MIT License

Links
Synced at

Sat, 02 May 2026 03:09:23 GMT

Languages