KEV::Client
HTTP client for the CISA KEV feed.
The default endpoint is the canonical JSON feed:
https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json
catalog = KEV::Client.fetch
puts catalog.size
For repeated polling (e.g. a CI job), prefer a long-lived instance and
use fetch_if_modified so unchanged feeds short-circuit without a full
download:
client = KEV::Client.new
if catalog = client.fetch_if_modified
process(catalog)
end
Constants
CISA's CSV mirror of the same catalog. Same per-row data, no
catalog-level metadata (catalogVersion, dateReleased, count).
Default ceiling for redirect chasing in fetch. Most KEV requests
answer 200 directly, so the default is 0: no redirects are followed
and a 3xx response raises FetchError. Raise this (via the
max_redirects argument) when pointing the client at a mirror or proxy
that issues a canonical redirect.
Default number of retries (in addition to the initial attempt) for transient failures. A single CISA hiccup — a 503 behind their CDN, a reset connection, a read timeout — should not surface to the caller, so the request is retried with exponential backoff before giving up.
Base delay for the first backoff sleep. Subsequent retries double this
(capped at MAX_BACKOFF) and add jitter.
CISA's published JSON feed URL — the schema-bound source of truth.
User-Agent sent on every request. Identifies the client so CISA can
contact maintainers if the feed format changes — and so this library
is not silently lumped in with anonymous scrapers.
Status codes that carry a Location the client is expected to
follow — the same set HTTP::Client itself chases.
Deliberately not the whole 3xx range: HTTP::Status#redirection?
answers true for 304 Not Modified (and for 305/306), none of which
carry a Location. Testing that predicate made a plain fetch
against a caching intermediary report "redirect … missing Location
header" instead of the actual status.
Upper bound for a single exponential-backoff sleep. Without a cap the doubling delay grows unbounded and can strand a fiber for minutes; 30s is plenty to let a transient outage clear.
HTTP status codes worth retrying: rate-limiting plus the transient gateway/server-side 5xx family. A 4xx (404, 403, …) is the caller's problem and is never retried.
Constructors
Class methods
Instance methods
Exponential backoff with a hard cap and decorrelated jitter:
base * 2^(attempt-1), clamped to MAX_BACKOFF, plus up to 10% jitter.
The cap keeps a long retry chain from sleeping for minutes, and the jitter spreads concurrent retries so they don't all wake at once. This is library runtime code (not a deterministic workflow), so a random source is acceptable here.
Public so the cap/jitter bounds can be unit-tested without sleeping.
Fetch and parse the catalog. Raises FetchError on any transport
or HTTP-level failure (including a non-2xx response — see below)
and KEV::ParseError on a schema-malformed body.
JSON::ParseException propagates unchanged for raw JSON syntax
errors, matching the cvss.cr precedent.
NOTE: redirects are not followed by default (max_redirects is
0). CISA's canonical feed URL has been stable, so a 3xx raises
FetchError rather than silently following to a possibly untrusted
host — resolve the final URL yourself and pass it to initialize,
or raise max_redirects if you are pointing at a mirror or proxy
that issues a canonical redirect.
Fetch the CSV form of the catalog from the configured url. CSV has
no catalog metadata, so the synthesized catalog_version and
date_released arguments flow through to Catalog.parse_csv.
Conditional fetch using If-None-Match / If-Modified-Since.
Returns the new catalog on a 200 response and nil on a 304.
First call (no validators recorded yet) behaves like fetch.
A 304 still refreshes last_etag / last_modified when the server
sends updated ones, so a long-lived poller tracks validator rotation
instead of pinning the first pair it ever saw.
Last ETag observed on a successful fetch (or nil if the server
did not return one). Used by fetch_if_modified.
Maximum number of HTTP redirects to follow. 0 (the default) keeps
the original strict behavior — a 3xx response raises FetchError.
Set this when pointing the client at a mirror or proxy that issues a
canonical redirect.
Number of retries (beyond the first attempt) for transient failures.
0 restores the original single-attempt behavior.
Base backoff delay; doubled per retry, capped at MAX_BACKOFF, plus
jitter. Injectable so tests can drive the retry path with a near-zero
delay and stay fast.