class

KEV::Vulnerability

Inherits Comparable < Reference < Object

A single entry from the CISA Known Exploited Vulnerabilities catalog.

Mirrors the official JSON schema published at https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities_schema.json

vuln = KEV::Vulnerability.from_json(payload)
vuln.cve_id            # => "CVE-2024-1234"
vuln.vendor_project    # => "Microsoft"
vuln.known_ransomware? # => true
vuln.overdue?          # => false

Equality is structural: two vulnerabilities are == only when every field matches, so re-parsed snapshots compare equal exactly when their content is identical.

For the common dedup case ("is this the same CVE, possibly from a different snapshot?") use Vulnerability#same_cve?(other) instead.

Ordering is by date_added (chronological), with cve_id as a tiebreak so the sort is stable across entries added on the same day. Entries that tie on both keep comparing through the remaining fields, so the Comparable contract holds: a == b ⟺ (a <=> b) == 0.

Constants

CVE_ID_PATTERN = /\ACVE-[0-9]{4}-[0-9]{4,}\z/

Schema-enforced CVE id shape: CVE-YYYY-N{4,}. Matches the official CISA KEV JSON schema pattern ^CVE-[0-9]{4}-[0-9]{4,}$ — a 4-digit year and a serial of at least four digits with no upper bound. See https://cve.mitre.org/cve/identifiers/syntaxchange.html for the 2014 widening from a fixed 4-digit serial.

CWE_PATTERN = /\ACWE-[0-9]+\z/

Schema-enforced CWE shape for entries inside the cwes array.

Constructors

from_json(input : String | IO) : Vulnerability

Parses a single Vulnerability from a raw JSON string. Useful in tests and when ingesting per-CVE payloads from upstream stores.

Source
from_json_any(any : JSON::Any) : Vulnerability

Builds a Vulnerability from a parsed JSON::Any object (a single element of the catalog's vulnerabilities array).

Raises MissingFieldError when a schema-required field is absent or ParseError when a date string is malformed.

Source
new(cve_id : String, vendor_project : String, product : String, vulnerability_name : String, date_added : Time, short_description : String, required_action : String, due_date : Time, known_ransomware_campaign_use : RansomwareUse | Nil = nil, notes : String | Nil = nil, cwes : Array(String) = [] of String, known_ransomware_campaign_use_raw : String | Nil = nil)
Source

Class methods

cwe_number(code : String) : Int32 | Nil

The numeric part of a CWE reference, or nil when code is not one. Accepts every spelling #has_cwe? does.

Source
cwe_sort_key(code : String) : Tuple(Int32, String)

Sort key that orders CWE codes by weakness number rather than as text, so CWE-20 precedes CWE-100. Codes with no numeric part (or a number too large for Int32) sort last, by literal string, so the ordering stays total.

Source

Instance methods

<=>(other : Vulnerability) : Int32 | Nil

Sort by date_added, then cve_id for stability.

(date_added, cve_id) is not unique across snapshots — CISA edits descriptions, notes, and CWEs in place — so a pair that ties on both falls through to the remaining fields in a fixed order. That keeps the Comparable contract this class documents: (a <=> b) == 0 exactly when a == b.

Source
==(other : Vulnerability) : Bool

Structural equality — every field must match. See the class doc for the rationale (Comparable contract preservation).

Source
cisa_url

CISA's catalog page for this CVE.

Source
cve_id

The CVE ID, e.g. "CVE-2024-1234". Schema guarantees the CVE-YYYY-N{4,19} shape.

Source
cve_year

The 4-digit (or longer) year of the CVE assignment. Raises KEV::ParseError if the CVE id does not have a parseable year component (cannot happen for schema-validated input, but defensive against programmatically-constructed Vulnerabilities).

Source
cwes

Associated Common Weakness Enumeration codes (e.g. ["CWE-79"]). Always an array — empty when the field is omitted in the source feed.

Returns a copy: initialize defensively copies the array it is handed, and this accessor completes the seal at the read end, so a Vulnerability can never be mutated through the array it hands out. For read-only traversal prefer #each_cwe, which does not allocate; for membership tests prefer #has_cwe?.

Source
cwes_size

Number of CWE codes on this entry, without materialising #cwes.

Source
date_added

Date the vulnerability was added to the KEV catalog. CISA publishes this as YYYY-MM-DD (no time component); represented here as a UTC midnight Time.

Source
days_until_due(now : Time = Time.utc) : Int32

Whole calendar days from now to due_date, in UTC. 0 on the due date itself (still on time — see #overdue?), positive while the deadline is ahead, negative once it has passed.

Source
due_date

Federal-agency remediation deadline. Same YYYY-MM-DD UTC-midnight representation as date_added.

Source
each_cwe

Yield each CWE code without copying the backing array. Use this on hot paths (grouping, counting) where #cwes would allocate a copy per entry.

Source
has_cwe?(code : String) : Bool

Case-insensitive CWE lookup. Accepts "CWE-79", "cwe-79", "Cwe-79", a bare "79", or zero-padded forms like "CWE-079" — the numeric portion is compared as an integer, so prefix casing, surrounding whitespace, widths, and leading zeros don't matter.

Source
hash(hasher)

See Object#hash(hasher)

Source
inspect(io : IO) : Nil

Appends a String representation of this object which includes its class name, its object address and the values of all instance variables.

class Person
  def initialize(@name : String, @age : Int32)
  end
end

Person.new("John", 32).inspect # => #<Person:0x10fd31f20 @name="John", @age=32>
Source
known_ransomware?

true when CISA has confirmed ransomware-campaign exploitation. Maps nil and Unknown both to false — the predicate name asks about confirmed use, and only Known answers yes.

Source
known_ransomware_campaign_use

Whether CISA has confirmed ransomware-campaign use. nil only for legacy entries that predate the field — current feed entries always populate it. Unknown / future strings (the schema declares this as a bare type: string, with no enum constraint) parse as nil here and are preserved verbatim in known_ransomware_campaign_use_raw.

Source
known_ransomware_campaign_use_raw

The raw string as it appeared in the feed. Populated for every entry whose source JSON contained knownRansomwareCampaignUse, including values that do not match the typed enum (Known / Unknown). Round-trip JSON serialization prefers this over known_ransomware_campaign_use so future CISA additions do not silently lose data.

Source
mitre_url

MITRE CVE record page for this CVE.

Source
notes

Free-form notes, often reference URLs separated by ;. nil when absent from the feed entry.

Source
nvd_url

NVD detail page for this CVE.

Source
overdue?(now : Time = Time.utc) : Bool

true when the remediation deadline has passed (relative to now, default Time.utc).

CISA's dueDate names the last day on which remediation is still on time, and is carried here as UTC midnight opening that day. An entry is therefore overdue only once the whole due day has elapsed — i.e. from due_date + 1 day onward. Comparing due_date < now directly would flag every entry as overdue from one second past midnight on the day it is actually still due.

Source
product

Affected product name, e.g. "Microsoft Exchange Server".

Source
remediation_window_days

Days between date_added and due_date. Useful for spotting unusual remediation windows (CISA usually sets ~21 days, but some entries get tighter or much wider deadlines).

Source
required_action

Remediation guidance — typically references BOD 22-01.

Source
same_cve?(other : Vulnerability) : Bool

True when both records describe the same CVE, regardless of any other field differences. Use this (not ==) to dedup across feed snapshots where CISA may have edited descriptions, notes, or CWEs.

Source
short_description

One-paragraph description from CISA.

Source
summary(now : Time = Time.utc) : String

One-line, terminal-friendly summary. Useful for logging or rendering a quick kev | grep pipeline.

Source
to_csv_row(builder : CSV::Builder) : Nil

Emit this entry as a single CSV row in the canonical CISA column order (see Catalog::CSV_HEADERS). Multiple CWEs are joined with ", " to match how CISA serializes them.

Source
to_h

Returns a Hash(String, String | Array(String)) keyed by the canonical KEV feed field names. Dates are serialised as YYYY-MM-DD strings. Optional fields are omitted when not present.

Source
to_json(json : JSON::Builder) : Nil

JSON serialization. Field order and naming match the canonical KEV feed, so to_json output is interchangeable with rows in vulnerabilities from the official catalog.

Source
valid?

true when validate! would succeed.

Source
validate!

Re-run the schema-level shape checks against this record. Useful after programmatic construction or in-place mutation, when fields may have skipped the parser path that normally validates them. Raises KEV::InvalidValueError on the first violation.

Source
vendor_project

Vendor or upstream project name, e.g. "Microsoft".

Source
vulnerability_name

Human-readable vulnerability name set by CISA.

Source