EPSS::CSV
Parser for the public EPSS daily feed published at
https://epss.empiricalsecurity.com/epss_scores-YYYY-MM-DD.csv.gz
(the prior host https://epss.cyentia.com/... still mirrors the same
file and is accepted by CSV.feed_url(..., host: ...)).
Format (verbatim, leading # line, then a header row, then rows):
#model_version:v2025.03.14,score_date:2026-05-18T00:00:00+0000
cve,epss,percentile
CVE-1999-0001,0.0046,0.7385
CVE-1999-0002,0.0452,0.9217
...
The # line is a single metadata comment carrying the model version
and the publication timestamp. CSV.parse extracts both into a
Metadata struct and stamps every Score row's date with the
feed's score_date.
Constants
Canonical host that publishes the gzipped daily EPSS feed.
Instance methods
Yield each Score without buffering the whole feed in memory. Useful
for the full daily file (200k+ rows).
File.open("epss_scores-2026-05-18.csv.gz") do |raw|
EPSS::CSV.each_score(raw) do |score|
index[score.cve] = score
end
end
Build the canonical feed URL for a given UTC date. The FIRST EPSS
team publishes one file per day at this exact path; both the new
empiricalsecurity.com host and the legacy cyentia.com host
serve identical content.
EPSS::CSV.feed_url(Time.utc(2026, 5, 18))
# => URI("https://epss.empiricalsecurity.com/epss_scores-2026-05-18.csv.gz")
Download and parse the daily feed for date. Delegates to
EPSS.client.fetch_feed, which routes the request through the
client's transport, retry, and timeout pipeline. To inject a stub
transport or use a non-default base, call client.fetch_feed
directly.
feed = EPSS::CSV.fetch(Time.utc(2026, 5, 18))
feed.scores.size # => 240000+