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.
| Scanner | Where the CVE ids come from |
|---|---|
osv | the vulnerability id and its aliases (a GHSA-… advisory lists its CVE as an alias) |
grype | the id and its relatedVulnerabilities |
trivy | reports 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
| Feed | Source | Content |
|---|---|---|
| KEV | CISA known_exploited_vulnerabilities.json | CVEs known to be exploited: date added, remediation due date, whether it is used in ransomware campaigns |
| EPSS | FIRST 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_countandmax_epssof every stored vulnerability result are recomputed, in batches. A finding scanned later gets them immediately. - Exploitation signals are on by default (
exploit.enabled: true). Withexploit.enabled: falsenothing 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:
| Field | Meaning |
|---|---|
cve | the CVE id the signals were looked up with, or empty |
kev | true when the CVE is in the KEV catalog |
kev_date_added, kev_due_date | KEV dates, when kev is true |
kev_ransomware | KEV's known ransomware campaign use flag |
epss | EPSS score (probability, 0 to 1), null when unscored |
epss_percentile | the score's percentile, null when unscored |
epss_date | the score date, null when unscored |
priority | the derived priority of this vulnerability |
Each result row (the vulnerability overview, the repository's scan list and the security findings list) has the aggregates:
| Field | Meaning |
|---|---|
kev_count | number of the result's vulnerabilities that are in KEV |
max_epss | the highest EPSS score among them, null when none is scored |
priority | the 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 aboveexploit.epssThreshold(default0.1).- otherwise the severity (
critical,high,medium,low;nonefor 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:
| Query | Meaning |
|---|---|
sort | severity (default, the existing order) or priority: highest priority first, then the existing tie-breakers |
kev | true: only rows with kev_count > 0; false: only rows with none |
epss_min | a number from 0 to 1: rows whose max_epss is at least that |
priority | kev, 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=highThe 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).
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 falsejq -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:
{"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
| Metric | Type | Labels | Meaning |
|---|---|---|---|
openartifact_exploit_import_attempts_total | counter | feed, result | imports per feed (kev, epss); result is imported, unchanged or failed |
openartifact_exploit_feed_last_success_timestamp_seconds | gauge | feed | Unix time of the last import or unchanged check |
A staleness alert, for the default daily interval:
- alert: OpenArtifactExploitFeedStale
expr: time() - openartifact_exploit_feed_last_success_timestamp_seconds > 3 * 24 * 3600
for: 1hSee 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.jsoncatalog, unmodified. - EPSS: a gzip-compressed CSV whose first line is a comment
#model_version:…,score_date:…and whose header iscve,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.