ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Osv.Epss

Description

The FIRST.org EPSS feed, the exploitability score Pilot joins onto each advisory.

Pilot joins the scores through advisory aliases (Ecluse.Core.Osv.Advisory). Every compile attempts the feed. A failed fetch is an EpssFeedFailure, and the ecosystem's EpssRequirement decides whether it stops publication. Individual missing scores remain absent.

Synopsis

The feed

maxEpssFeedBytes :: Int Source #

The byte ceiling Pilot fetches under, 64 MiB, applied to the served stream and again to its expansion. The feed is one short row per scored CVE, so the headroom is several times over.

maxEpssLineBytes :: Int Source #

The longest feed line Pilot holds, 4 KiB. A scored row is under 100 bytes, so a longer line is no row, and bytes that never reach a newline cannot pile up.

data EpssFeed Source #

One fetch of the feed: the scores it carries, and what it says about itself. The feed declares its own score date, so a stalled feed is visible without a second source of truth.

Constructors

EpssFeed 

Fields

Instances

Instances details
Show EpssFeed Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

Eq EpssFeed Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

fetchEpssScores :: (MonadResource m, MonadThrow m, KatipContext m) => Int -> String -> m EpssFeed Source #

Fetch the feed and decode it into a score table, bounded by cap bytes on each side of decompression. A non-2xx, undecodable, over-large, or scoreless feed throws.

data EpssFeedTooLarge Source #

The feed passed a byte ceiling, so the fetch refused it whole. Each carries that ceiling and the bytes seen when it tripped, which is the ceiling plus at most one chunk.

Constructors

CompressedTooLarge Int Int

The compressed stream the host served, so an endless one cannot hang the pass.

DecompressedTooLarge Int Int

Its expansion under gzip, which is what a compression bomb inflates.

LineTooLarge Int Int

One line of the expansion (maxEpssLineBytes), however the stream was chunked.

data EpssFeedEmpty Source #

The feed decoded to no scores at all: an error page served as 200, or a column order the row decode no longer reads. Whole-feed failure remains distinct from an individual missing score.

Constructors

EpssFeedEmpty 

data EpssFeedTruncated Source #

A gzip member ended before its end-of-stream marker, so the rows it carried may be a fraction.

Constructors

EpssFeedTruncated 

One compile's attempt

data EpssFeedFailure Source #

Why one attempt at the feed produced no scores.

Constructors

EpssFeedStatus Int

The feed host answered with this non-2xx status.

EpssFeedTransport TransportCause

The transport could not deliver the feed.

EpssFeedOversize EpssFeedTooLarge

The feed passed a byte ceiling.

EpssFeedUndecodable

The feed is not valid gzip, or its stream ended early.

EpssFeedNoScores

The feed decoded to no scores.

renderEpssFeedFailure :: EpssFeedFailure -> Text Source #

The failure as an operator reads it. It never names the feed URL, which can carry a credential.

classifyEpssFailure :: SomeException -> Maybe EpssFeedFailure Source #

The feed failures a compile can continue past. An invalid URL is a configuration fault, and anything unnamed here may be a bug, so both stay exceptions rather than read as an outage.

acquireEpssFeed :: (MonadResource m, MonadMask m, MonadUnliftIO m, KatipContext m) => Int -> String -> m (Either EpssFeedFailure EpssFeed) Source #

Fetch the feed under the advisory retry policy. A failure classifyEpssFailure names returns as a value, and every other exception, a cancellation included, propagates.

data EpssEnrichment Source #

What one compile joins onto its advisories.

Constructors

EpssEnriched EpssFeed

The feed arrived, so its scores join and its provenance is recorded.

EpssUnavailable EpssFeedFailure

The feed failed where the ecosystem does not require it, so no score joins.

Instances

Instances details
Show EpssEnrichment Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

Eq EpssEnrichment Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

resolveEnrichment :: EpssRequirement -> Either EpssFeedFailure EpssFeed -> Either EpssFeedFailure EpssEnrichment Source #

Settle one attempt under the ecosystem's requirement. A failure stays Left where the ecosystem requires enrichment, so an unavailable feed never stands in for a required one.

enrichedFeed :: EpssEnrichment -> Maybe EpssFeed Source #

The feed an enrichment joined, if one arrived.

enrichmentStatus :: EpssEnrichment -> EpssStatus Source #

The status the artifact records for this enrichment.

The score table

data EpssScores Source #

The scores from one fetch of the feed. Keys are upper-cased CVE ids, so a case difference between the feed and an advisory's aliases cannot silently miss the join.

Instances

Instances details
Show EpssScores Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

Eq EpssScores Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

mkEpssScores :: [(Text, Double)] -> EpssScores Source #

Build a score table from (CVE id, probability) rows. A duplicate id keeps the higher score, the fail-closed direction for a rule that denies above a threshold.

epssForIds :: EpssScores -> [Text] -> Maybe Double Source #

The highest score the feed carries for any of these identifiers, or Nothing when it scores none of them.

epssScoreCount :: EpssScores -> Int Source #

How many CVEs the table scores.

One feed row

parseEpssLine :: ByteString -> Maybe (Text, Double) Source #

One feed row as (CVE id, probability). A comment, the header, an unreadable row, and a score outside [0, 1] all yield Nothing, so one bad row drops out instead of failing the pass.

The feed's preamble

data EpssPreamble Source #

What the feed's leading comment line declares. FIRST.org writes it as #model_version:v2026.08.01,score_date:2026-08-29T00:00:00+0000.

Instances

Instances details
Show EpssPreamble Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

Eq EpssPreamble Source # 
Instance details

Defined in Ecluse.Core.Osv.Epss

parseEpssPreamble :: ByteString -> EpssPreamble Source #

Read the feed's leading comment line. A line that is not a comment, a comment naming neither field, and a date the grammar cannot read all yield absence, never a substitute value.