GitHub
ESC

Vulnerability

KEV::Vulnerability

A single entry from the CISA KEV catalog. Mirrors the official KEV JSON schema.

Includes Comparable(Vulnerability). Equality is structural over every field — see Equality and ordering below.

Fields

Getter Type Required by schema Description
cve_id String The CVE ID (e.g. "CVE-2024-1234").
vendor_project String Vendor / upstream project.
product String Affected product.
vulnerability_name String CISA-set name.
date_added Time UTC midnight; source is YYYY-MM-DD.
short_description String One-paragraph description.
required_action String Remediation guidance.
due_date Time Federal-agency deadline; UTC midnight.
known_ransomware_campaign_use RansomwareUse? optional Known or Unknown, or nil when absent.
notes String? optional Free-form notes (often reference URLs separated by ;).
cwes Array(String) optional Associated CWE codes; always emitted on serialisation (as [] when empty) to match the live feed.

Construction

KEV::Vulnerability.new(
  cve_id: "CVE-2024-1234",
  vendor_project: "ACME",
  product: "Widget",
  vulnerability_name: "ACME Widget RCE",
  date_added: Time.utc(2024, 1, 1),
  short_description: "...",
  required_action: "Apply updates.",
  due_date: Time.utc(2024, 1, 22),
  known_ransomware_campaign_use: KEV::RansomwareUse::Unknown,
  notes: nil,
  cwes: ["CWE-79"],
)

The cwes array is dup'd on construction — external mutation of the source array won't bleed into the Vulnerability's state.

Method Description
Vulnerability.from_json(input : String | IO) : Vulnerability Parse a single entry from a JSON object.
Vulnerability.from_json_any(any : JSON::Any) : Vulnerability Build from an already-parsed JSON::Any.

Predicates

Method Description
known_ransomware? : Bool true only when CISA confirmed ransomware use (Known).
overdue?(now : Time = Time.utc) : Bool due_date < now.
days_until_due(now : Time = Time.utc) : Int32 Negative once overdue.
remediation_window_days : Int32 dueDate - dateAdded, rounded to whole days.
has_cwe?(code : String) : Bool Numeric-width-normalised CWE check.
cve_year : Int32 The 4-digit year segment of the CVE id. Raises KEV::ParseError if unparseable.
same_cve?(other : Vulnerability) : Bool Same CVE id, regardless of other field differences.

Equality and ordering

== is structural: every field must match. This keeps the Comparable contract intact (a == b ⟺ (a <=> b) == 0).

For dedup across feed snapshots — where CISA may have edited descriptions or notes — use #same_cve?(other) instead of ==.

<=> orders by date_added (chronological), with cve_id as a stable tiebreak.

catalog.sort.first         # oldest entry
catalog.to_a.sort_by(&.due_date)  # by deadline

JSON

Method Description
to_json(json : JSON::Builder) : Nil Canonical CISA-shaped serialisation.
to_json : String Convenience wrapper.
to_h : Hash(String, String | Array(String)) Plain Hash keyed by canonical field names.

to_json and to_h always emit cwes (as [] when empty) to match the live feed's behaviour. notes and knownRansomwareCampaignUse are omitted when nil.

See also