JSON & Filters
NVD-shaped JSON serialization and classification helpers for filtering
NVD-shaped JSON output
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/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F/RL:O/RC:C",
"baseScore": 9.8,
"baseSeverity": "CRITICAL",
"exploitabilityScore": 3.9,
"impactScore": 5.9,
"temporalScore": 9.1,
"temporalSeverity": "CRITICAL"
}
Per-version differences:
- v1.0, v2 and v3.x add
temporalScore/temporalSeverityandenvironmentalScore/environmentalSeverityonly when the corresponding optional metrics are present in the vector. - v3.x always includes
exploitabilityScoreandimpactScore(rounded to one decimal, matching NVD). - v4.0 adds
macroVectorinstead of separate temporal/environmental fields.
Reading JSON
CVSS.from_json accepts:
- a flat object with a
vectorStringfield, - an NVD-nested payload (
{"cvssData": {"vectorString": "..."}}), or - any object or array with a
vectorStringsomewhere inside it — which covers a whole NVD API 2.0 response or CVE record unmodified, and a bare list of them
CVSS.from_json(%({"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}))
# => CVSS::V3::Vector
CVSS.from_json(File.read("nvd_response.json"))
The two flat shapes are a lookup — the caller pointed at the vector, so a malformed one there is an error. Searching the rest of a document is not, so entries the search cannot use (a placeholder "", a null, a CVSS version this library does not implement) are skipped rather than allowed to hide a real vector sitting beside them. Only a scalar or null payload is rejected outright, since it has no depth to search.
A record commonly scores one CVE under several CVSS versions at once. CVSS.from_json returns the first usable vector in document order; CVSS.from_json_all returns every one of them:
vectors = CVSS.from_json_all(File.read("nvd_response.json"))
vectors.map(&.version) # => ["3.1", "2.0"]
vectors.max_by(&.base_score) # worst score across versions
from_json_all raises like parse if any vectorString in the payload is malformed — a caller asking for all the vectors cannot be handed a quietly short list — and returns an empty array when the payload holds none at all.
CVSS.from_json? is the non-raising form. It returns nil for everything from_json rejects, including the JSON::ParseException raised when the input is not JSON at all:
if vec = CVSS.from_json?(untrusted_payload)
# use vec
end
The library only trusts the vectorString — baseScore and similar fields in the input are ignored, and scores are recomputed from the parsed vector. This makes parsing safe against tampered or stale payloads.
Round-tripping via JSON
original = CVSS.parse("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:F")
reconstruct = CVSS.from_json(original.to_json)
reconstruct == original # => true
Classification helpers
Predicate methods make filtering large vulnerability lists ergonomic:
vec.network? # AV:N
vec.adjacent_network? # AV:A
vec.local? # AV:L
vec.physical? # AV:P (v3, v4)
vec.requires_privileges? # PR != N (v3, v4)
vec.requires_authentication? # Au != N (v2), Au:R (v1)
vec.remote? # AV:R (v1 only; network? is an alias)
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?
Severity predicates come for free from the Severity enum:
vec.severity.critical?
vec.severity >= CVSS::Severity::High
Filtering example
vulns = inputs.map { |s| CVSS.parse(s) }
# All network-reachable, no-privilege issues sorted worst-first
high_risk = vulns
.select { |v| v.responds_to?(:network?) && v.network? }
.reject(&.requires_privileges?)
.sort
.reverse
# Anything that crosses scope or hits the subsequent system
crossing = vulns.select do |v|
(v.is_a?(CVSS::V3::Vector) && v.scope_changed?) ||
(v.is_a?(CVSS::V4::Vector) && v.impacts_subsequent_system?)
end