class

KEV::Catalog

Inherits Indexable < Enumerable < Iterable < Enumerable < Reference < Object

The full CISA Known Exploited Vulnerabilities catalog.

A Catalog is Enumerable and Indexable over its vulnerabilities, so the usual collection methods work directly:

catalog = KEV::Catalog.parse(File.read("kev.json"))
catalog.size                             # => 1592
catalog.first.cve_id                     # => "CVE-2021-..."
catalog.select(&.known_ransomware?).size # => 300+

Catalog#query returns a chainable Query for more elaborate filters. Direct accessors are provided for the most common lookups (by_vendor, by_cwe, find, []).

Constants

CSV_HEADERS = ["cveID", "vendorProject", "product", "vulnerabilityName", "dateAdded", "shortDescription", "requiredAction", "dueDate", "knownRansomwareCampaignUse", "notes", "cwes"] of ::String

Column names CISA emits in the CSV feed, in source order. The CSV has no metadata (catalogVersion, dateReleased, count), so callers supply those via the catalog_version / date_released arguments — defaults give a stable but obviously-synthetic identity.

Constructors

from_json(input : String | IO) : Catalog

Alias for parse, mirroring the cvss.cr API surface.

Source
from_json_any(any : JSON::Any) : Catalog
Source
new(catalog_version : String, date_released : Time, count : Int32, vulnerabilities : Array(Vulnerability), title : String | Nil = nil)
Source
parse(input : String | IO) : Catalog

Parse a Catalog from a raw JSON string or IO.

Source
parse_csv(input : String | IO, catalog_version : String = "csv", date_released : Time = Time.utc, title : String | Nil = nil) : Catalog

Parse the CSV form of the catalog. CISA publishes a CSV alongside the JSON feed; it carries the same per-row fields but no catalog-level metadata, so the metadata defaults are synthetic.

catalog = KEV::Catalog.parse_csv(File.read("kev.csv"))
Source

Class methods

parse?(input : String | IO) : Catalog | Nil

Non-raising parse — returns nil on malformed input.

Source
parse_csv?(input : String | IO, **kwargs) : Catalog | Nil

Non-raising CSV parse — returns nil on malformed input.

Source

Instance methods

[](cve_id : String) : Vulnerability

find that raises KeyError on miss — mirrors Hash#[].

Source
[]?(cve_id : String) : Vulnerability | Nil

find that returns nil on miss — mirrors Hash#[]?.

Source
added_on_or_after(date : Time) : Array(Vulnerability)

Entries added on or after the given date.

Source
added_on_or_before(date : Time) : Array(Vulnerability)

Entries added on or before the given date.

Source
by_cwe(code : String) : Array(Vulnerability)

All entries tagged with the given CWE (e.g. "CWE-79" or "79").

Source
by_product(name : String) : Array(Vulnerability)

All entries for a given product (exact, case-insensitive match).

Source
by_vendor(name : String) : Array(Vulnerability)

All entries for a given vendor (exact, case-insensitive match).

Source
catalog_version

Calendar-style version string (e.g. "2026.05.15").

Source
count

CISA-reported entry count — not necessarily equal to vulnerabilities.size, since the value is what CISA reported at publish time. Use size (or vulnerabilities.size) for the in-memory count.

Source
cwes

All distinct CWE codes referenced in the catalog, sorted by weakness numberCWE-20, CWE-79, CWE-100. A plain string sort would put CWE-100 ahead of CWE-20, which is not an order any reader of a CWE list expects.

Source
date_released

The catalog publish timestamp as a Time (UTC).

Source
diff(other : Catalog) : Diff

Compare two snapshots by CVE id. Returns a Diff describing what was added, removed, or modified relative to self. The receiver is the "before" snapshot; other is the "after".

Source
due_within(span : Time::Span, now : Time = Time.utc) : Array(Vulnerability)

Entries due within the given time span from now and not yet overdue. "Not yet overdue" uses Vulnerability#overdue?, so an entry whose deadline is today still counts as upcoming.

Source
each

Iteration / Indexable contract

Source
find(cve_id : String) : Vulnerability | Nil

Look up by CVE id. O(1) after the first call (the CVE → entry index is memoised on first lookup).

The memo is built lazily and not synchronised — if you share a Catalog across preemptive threads, call find (or any other by-CVE lookup) once on the owning thread before publishing the reference, or guard the catalog with your own mutex.

The memo auto-invalidates when the underlying vulnerabilities array grows or shrinks (push/pop/concat). If you replace an entry in place with a different cve_id, the size doesn't change and the index won't notice — call reindex! explicitly in that case.

Source
group_by_cwe

Group every entry by each of its CWE codes. A vulnerability with multiple CWEs appears under each one. Entries without CWEs do not contribute to the result.

The returned Hash carries no default block, so looking up a CWE that is not in the catalog raises KeyError (and []? returns nil) rather than quietly inserting an empty bucket.

Source
group_by_ransomware

Group by the typed RansomwareUse value. Entries with no knownRansomwareCampaignUse (legacy rows) are grouped under nil.

Source
group_by_vendor

Group every entry by vendor (verbatim string, not case-folded).

Source
group_by_year

Group every entry by cve_year. Years map to Array(Vulnerability) in source-feed order (no sort on the inner arrays).

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
latest(n : Int32 = 10) : Array(Vulnerability)

The N most recently added entries (newest first). Ties on date_added are broken by cve_id for stability.

Source
oldest(n : Int32 = 10) : Array(Vulnerability)

The N oldest entries (earliest first). Ties broken by cve_id.

Source
overdue(now : Time = Time.utc) : Array(Vulnerability)

Entries whose remediation due date has already passed (relative to now, default Time.utc).

Source
products

All distinct products, sorted.

Source
query

Start a chainable Query over this catalog's entries.

Source
ransomware

All entries flagged as known ransomware-campaign exploits.

Source
reindex!

Drop the memoised CVE → entry index. The next find / [] call rebuilds it from the current vulnerabilities array. Use this after in-place edits that change a cve_id without changing the array's length.

Source
search(query : String) : Array(Vulnerability)

Case-insensitive substring match across the user-facing text fields: cve_id, vulnerability_name, short_description, vendor_project, and product. Useful for a single "give me everything mentioning log4j" hit without writing a custom where block.

Source
size

Returns the number of elements in this container.

Source
stats(top : Int32 = 5, now : Time = Time.utc) : Stats

Compute a Stats summary in a single pass. See KEV::Stats.

Source
title

Catalog metadata title. CISA hardcodes this to "CISA Catalog of Known Exploited Vulnerabilities" in the current feed, but it is not part of the schema's required fields, so we keep it nilable.

Source
to_csv

Emit the catalog in the canonical CSV form (matches CISA's CSV mirror byte-for-byte except where input was synthesized — the CSV has no metadata, so catalog_version / date_released are discarded). Round-trips through Catalog.parse_csv.

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

JSON serialization in the exact shape of the CISA feed. Round-trips cleanly via Catalog.parse(catalog.to_json).

Source
unsafe_fetch(index : Int) : Vulnerability

Returns the element at the given index, without doing any bounds check.

Indexable makes sure to invoke this method with index in 0...size, so converting negative indices to positive ones is not needed here.

Clients never invoke this method directly. Instead, they access elements with #[](index) and #[]?(index).

This method should only be directly invoked if you are absolutely sure the index is in bounds, to avoid a bounds check for a small boost of performance.

Source
valid?

true when every entry passes Vulnerability#valid?.

Source
validate!

Run Vulnerability#validate! on every entry. Use after constructing a Catalog from non-feed sources (e.g. fixtures, partial JSON, programmatic edits) to confirm schema-level shape compliance. Raises on the first offending entry.

Source
vendors

All distinct vendor names in the catalog, sorted.

Source
vulnerabilities

The decoded vulnerability entries, in source-feed order.

Source