Skip to content

Exploitation signals (KEV and EPSS) ​

Vulnerability findings carry two exploitation signals next to their severity: whether the CVE is in the CISA Known Exploited Vulnerabilities (KEV) catalog, and its FIRST EPSS score. They let you rank findings by what is likely to be attacked, not only by CVSS severity, and fail a pipeline on them.

The feeds are imported by the server (daily by default) into the database; the signals are attached to findings when they are read, so they are never stored in a scan's vulnerabilities and always reflect the data loaded at that moment. Configuration: Exploitation signals. Scans themselves: Scanning.

How a finding maps to a CVE ​

A vulnerability is looked up by its CVE ids: its own id when it is a CVE id, plus the CVE ids among its aliases.

ScannerWhere the CVE ids come from
osvthe vulnerability id and its aliases (a GHSA-… advisory lists its CVE as an alias)
grypethe id and its relatedVulnerabilities
trivyreports CVE ids directly

Some stored findings have no aliases. When the local OSV mirror holds the record, a finding with only a GHSA-style id is resolved to its CVE ids through the mirror, so it is annotated without a new scan. Without the mirror, such a finding has no CVE to look up until the package is scanned again. Findings with a CVE id are annotated straight away. A vulnerability with no CVE id gets no KEV or EPSS data and its priority is its severity.

The data and the import ​

FeedSourceContent
KEVCISA known_exploited_vulnerabilities.jsonCVEs known to be exploited: date added, remediation due date, whether it is used in ransomware campaigns
EPSSFIRST daily gzip CSV (epss_scores-current.csv.gz)per CVE, the EPSS score and its percentile, with the score date
  • A background worker imports both feeds once a day by default (exploit.interval, at least 1 h). With several replicas, only one imports at a time.
  • Requests are conditional (ETag / Last-Modified), so an unchanged feed is not downloaded again.
  • An empty, malformed, oversized or unreachable feed never replaces the data already loaded; the failure is recorded and the import is retried.
  • After each import the per-result aggregates kev_count and max_epss of every stored vulnerability result are recomputed, in batches. A finding scanned later gets them immediately.
  • Exploitation signals are on by default (exploit.enabled: true). With exploit.enabled: false nothing is imported or contacted; findings carry no KEV or EPSS data and their priority is their severity.

Fields ​

Each vulnerability in a result (GET …/vuln/scans/{id} and the exports that include vulnerabilities) has these additional fields, always present:

FieldMeaning
cvethe CVE id the signals were looked up with, or empty
kevtrue when the CVE is in the KEV catalog
kev_date_added, kev_due_dateKEV dates, when kev is true
kev_ransomwareKEV's known ransomware campaign use flag
epssEPSS score (probability, 0 to 1), null when unscored
epss_percentilethe score's percentile, null when unscored
epss_datethe score date, null when unscored
prioritythe derived priority of this vulnerability

Each result row (the vulnerability overview, the repository's scan list and the security findings list) has the aggregates:

FieldMeaning
kev_countnumber of the result's vulnerabilities that are in KEV
max_epssthe highest EPSS score among them, null when none is scored
prioritythe row's priority: the highest of its vulnerabilities

Priority ​

From highest to lowest: kev > epss > critical > high > medium > low > none.

  • kev: in the KEV catalog.
  • epss: not in KEV, EPSS score at or above exploit.epssThreshold (default 0.1).
  • otherwise the severity (critical, high, medium, low; none for a row with no findings). When a vulnerability's severity is unknown, its CVSS score decides the band.

Filters and sorts ​

GET /api/v1/vuln/scans and GET /api/v1/security/findings accept, in addition to their existing filters:

QueryMeaning
sortseverity (default, the existing order) or priority: highest priority first, then the existing tie-breakers
kevtrue: only rows with kev_count > 0; false: only rows with none
epss_mina number from 0 to 1: rows whose max_epss is at least that
prioritykev, epss, critical, high, medium or low: rows whose priority is exactly that (not "at least")

An invalid value is 400 validation_failed. A cursor belongs to the sort it was issued under; one from the other sort is rejected as a malformed cursor, so send the same sort and filters with it.

Gating a pipeline ​

GET /api/v1/repositories/{name}/vuln/scans/{id}/gate?kev=true&epss_min=0.5&min_priority=high

The gate checks the scan's vulnerabilities against the conditions you give and answers pass and the reasons. It fails when any finding meets ANY of the given conditions: it is in KEV (kev=true), has an EPSS score of at least epss_min, or has a priority of at least min_priority (kev, epss, critical, high, medium or low).

sh
RESULT=$(curl -sS -H 'Authorization: Bearer <TOKEN>' \
  "HOST/api/v1/repositories/REPO/vuln/scans/$SCAN_ID/gate?kev=true&epss_min=0.5&min_priority=high")
echo "$RESULT" | jq .
echo "$RESULT" | jq -e .pass > /dev/null   # exit status 1 when pass is false

jq -e exits non-zero when pass is false, which fails the CI step. Poll the scan until it is completed before calling the gate.

In the web UI ​

Settings > Security > Scan gate (admin only) shows the feed status, lists the open scans that meet a gate (known exploited, and/or EPSS at least a percentage you enter) and lets you check one scan against the gate. Checking a scan only reports. To stop downloads and promotion of a scan, block it from the same page: that quarantines it (403 ARTIFACT_QUARANTINED) until you release it. The page lists what is blocked.

Feed status ​

GET /api/v1/security/exploit-feeds (same access as the security overview) reports each feed's state:

json
{"feeds":[
  {"name":"kev","source_url":"https://…","version":"…","published_at":"…","entry_count":1500,"imported_at":"…","checked_at":"…","last_error":""},
  {"name":"epss","source_url":"https://…","version":"…","published_at":"…","entry_count":250000,"imported_at":"…","checked_at":"…","last_error":""}],
 "epss_threshold":0.1,
 "enabled":true}

enabled is false when exploit.enabled is off on this instance (no feeds are imported). checked_at is the last attempt, imported_at when data was last loaded; last_error is empty after a success. Timestamps are null before the first import.

Metrics ​

MetricTypeLabelsMeaning
openartifact_exploit_import_attempts_totalcounterfeed, resultimports per feed (kev, epss); result is imported, unchanged or failed
openartifact_exploit_feed_last_success_timestamp_secondsgaugefeedUnix time of the last import or unchanged check

A staleness alert, for the default daily interval:

yaml
- alert: OpenArtifactExploitFeedStale
  expr: time() - openartifact_exploit_feed_last_success_timestamp_seconds > 3 * 24 * 3600
  for: 1h

See also Monitoring.

Air-gapped operation ​

Mirror the two files on a host the server can reach and point exploit.kevUrl and exploit.epssUrl (OPENARTIFACT_EXPLOIT_KEV_URL, OPENARTIFACT_EXPLOIT_EPSS_URL) at them; both must be https URLs. A private mirror host is allowed, because the operator configured it. Refresh the mirrored files at least as often as the import interval.

  • KEV: the known_exploited_vulnerabilities.json catalog, unmodified.
  • EPSS: a gzip-compressed CSV whose first line is a comment #model_version:…,score_date:… and whose header is cve,epss,percentile.

Without outbound access and without a mirror, set exploit.enabled: false.

Data sources and terms ​

CISA KEV. The catalog is published by CISA under CC0 1.0 (licence text); the same catalog is at https://github.com/cisagov/kev-data. Use of the data does not authorize use of the CISA logo or the DHS seal, and does not imply endorsement. Information at third-party links in KEV notes remains under those sites' policies.

FIRST EPSS. EPSS is free of charge and needs no registration. FIRST requests attribution when EPSS data is used in publications or products:

EPSS scores are provided by FIRST (https://www.first.org/epss/); the model is developed and maintained by the EPSS Special Interest Group at FIRST, scores generated by Empirical Security.

  • The FIRST API (api.first.org/epss) is meant for lookups, not for bulk use or keeping a local copy. That is why OpenArtifact imports the daily CSV instead of calling the API.
  • FIRST states there is no universal threshold; 0.10 is commonly cited. The threshold is therefore configurable (exploit.epssThreshold).
  • EPSS estimates the probability of exploitation activity in the next 30 days. It is a signal for prioritisation, not a statement that a vulnerability is or is not being exploited.

Free software under the AGPL-3.0-or-later.