Cyber Exposure Intelligence (NVD + CISA KEV + FIRST EPSS) avatar

Cyber Exposure Intelligence (NVD + CISA KEV + FIRST EPSS)

Pricing

Pay per usage

Go to Apify Store
Cyber Exposure Intelligence (NVD + CISA KEV + FIRST EPSS)

Cyber Exposure Intelligence (NVD + CISA KEV + FIRST EPSS)

Joins the official NVD CVE API 2.0, the CISA Known Exploited Vulnerabilities catalog and FIRST EPSS into one deduplicated, risk-prioritized vulnerability record with incremental change tracking and durable cross-run state.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Atlas Assistent

Atlas Assistent

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

2 days ago

Last modified

Share

Turn three official, authoritative vulnerability feeds into one deduplicated, risk-prioritized CVE record you can pipe straight into a SIEM, a ticketing queue, a spreadsheet or a dashboard.

This Actor joins:

SourceEndpointRoleAuthority
NVD CVE API 2.0https://services.nvd.nist.gov/rest/json/cves/2.0Base entity (the CVE, CVSS, CWE, CPE, references)U.S. NIST National Vulnerability Database
CISA KEVhttps://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.jsonEnrichment: is it actively exploited?U.S. Cybersecurity and Infrastructure Security Agency
FIRST EPSShttps://api.first.org/data/v1/epssEnrichment: probability of exploitation (score + percentile)FIRST.org Exploit Prediction Scoring System

The output is one row per CVE with a transparent, deterministic risk-priority synthesis (critical / high / medium / low / unknown) that combines CVSS severity, EPSS likelihood and KEV membership — so "patch this first" is answered, not just "here are 1,000 CVEs".


Who it is for (ICP) & use cases

Ideal customer: vulnerability-management, threat-intel, and detection-engineering teams; MSSPs; GRC/compliance functions; and security-tooling vendors that need a clean, joined CVE feed without building and maintaining three separate ingestion pipelines.

Concrete use cases:

  • Risk-based patch prioritization. Rank your exposure by KEV membership + EPSS + CVSS instead of CVSS alone. Feed the risk.priority / risk.score into your patch SLA logic.
  • KEV compliance monitoring (e.g. BOD 22-01). Run with kevOnly: true to get exactly the CVEs on the CISA catalog, with their dueDate and requiredAction.
  • Continuous exposure watch for your stack. Filter by vendor / product / cpe (e.g. Apache, cpe:2.3:a:apache:log4j) and schedule incremental runs; you get only what is new or changed.
  • Emerging-threat triage. Filter epssMin: 0.5 or epssPercentileMin: 0.95 to surface CVEs the EPSS model considers likely to be exploited in the next 30 days.
  • Enrichment for an existing scanner. Provide a list of cveIds from your scanner and get back the joined NVD + KEV + EPSS + risk view for each.

Output schema

Every dataset item carries schemaVersion (currently 1.0.0) so downstream consumers can pin a version. recordId equals the canonical cveId — the stable, globally-unique key you can join on.

Key fields (see .actor/dataset_schema.json for the full contract):

{
"recordId": "CVE-2021-44228", // == cveId, the stable key
"schemaVersion": "1.0.0",
"source": "nvd",
"cveId": "CVE-2021-44228",
"vulnStatus": "Analyzed",
"description": "Apache Log4j2 JNDI ... (Log4Shell).",
"published": "2021-12-10T10:15:09.143Z",
"lastModified": "2024-05-20T12:00:00.000Z",
"cursorField": "lastModified", // which NVD date the incremental cursor keys on
"cursorDate": "2024-05-20T12:00:00.000Z",
"cvss": { "version": "3.1", "baseScore": 10, "baseSeverity": "CRITICAL", "vectorString": "...", "exploitabilityScore": 3.9, "impactScore": 6, "source": "nvd@nist.gov" },
"cweIds": ["CWE-502"],
"cpe": ["cpe:2.3:a:apache:log4j:2.0:*:*:*:*:*:*:*"],
"vendors": ["apache"],
"products": ["log4j", "Log4j2"],
"references": ["https://logging.apache.org/log4j/2.x/security.html"],
"enrichment": {
"kev": { "status": "ok", "listed": true, "dateAdded": "2021-12-10", "dueDate": "2021-12-24", "requiredAction": "...", "knownRansomwareCampaignUse": "Known", "vulnerabilityName": "..." },
"epss": { "status": "ok", "score": 0.94474, "percentile": 0.99988, "date": "2024-06-01" }
},
"risk": { "priority": "critical", "score": 98.6, "factors": ["cvss=10.0/v3.1","epss=0.9447","kev=listed+ransomware"], "rationale": "Priority critical: On the CISA KEV catalog and linked to ransomware campaigns; CVSS base 10.0; EPSS 0.9447 (p100.0)." },
"canonicalUrl": "https://nvd.nist.gov/vuln/detail/CVE-2021-44228",
"provenance": { "sources": ["nvd","kev","epss"], "endpoints": ["..."], "fetchedAt": "2024-06-01T00:00:00.000Z", "nvdLastModified": "2024-05-20T12:00:00.000Z" },
"firstSeen": "2024-06-01T00:00:00.000Z",
"lastSeen": "2024-06-01T00:00:00.000Z",
"changedFields": []
}

Enrichment status is always explicit

enrichment.kev.status and enrichment.epss.status are one of ok / failed / not_enriched. A failed status means the upstream feed could not be fetched this run — it is never silently collapsed into "not listed" / "no score". This lets you distinguish "genuinely not exploited" from "we could not check", and it drives the safe cursor behavior described below.

Risk-priority synthesis (how risk is computed)

A transparent, documented heuristic (not an official standard), inspired by CISA's SSVC framing and the KEV "if it's on the catalog, patch it" mandate:

  1. KEV membership dominates. A CVE with confirmed in-the-wild exploitation is at least high, and critical when it is used in ransomware campaigns or carries a critical CVSS. Active exploitation outranks any predicted-likelihood signal.
  2. Otherwise the priority is a monotone combination of CVSS severity and EPSS likelihood, so a severe flaw that is also likely to be exploited outranks one that is merely severe.
  3. unknown only when none of CVSS, EPSS or KEV provide any signal.

risk.score (0–100) is a fixed-weight blend for ranking within a priority bucket:

  • CVSS contributes up to 60 (baseScore / 10 × 60),
  • EPSS probability contributes up to 25 (score × 25),
  • KEV membership contributes up to 15 — exactly 12 for a KEV-listed CVE and 15 when it is additionally linked to ransomware campaigns.

The EPSS percentile and the CVSS severity label influence the priority bucket (the critical/high/… classification above) but are deliberately not part of the numeric score, to avoid double-counting the EPSS signal. The function is pure and order-independent — identical inputs always yield an identical result — and risk.factors / risk.rationale explain every decision.


Input

All fields are optional. See .actor/input_schema.json for editor metadata. Numeric inputs are strictly validated — a non-numeric or out-of-range value (e.g. maxItems: 0, cvssMin: 15) is rejected with a clear error, not silently clamped, so an API caller that bypasses the input-schema editor cannot get surprising truncated behavior.

FieldTypeDefaultDescription
modeenum incremental | fullincrementalincremental: only CVEs new/changed since the last run. full: backfill over the lookback window (never advances the cursor).
cursorFieldenum lastModified | publishedlastModifiedWhich immutable NVD date the incremental cursor orders by. lastModified also surfaces re-analysed CVEs.
maxItemsinteger200Cap on dataset items after filtering/enrichment/dedup.
lookbackDaysinteger30First-run incremental window and full-mode backfill window.
recheckWindowDaysinteger2Re-check already-committed CVEs whose cursor date is within this many days below the cursor, to surface upstream edits. Never regresses the cursor.
maxNvdPagesinteger25Hard ceiling on NVD pages per run (cost guard). When hit, the cursor is held below the uncaptured region.
nvdApiKeystring (secret)Optional NVD API key. Raises the rate limit; sent only as a header, never logged.
cveIdsstring[]Explicit CVE allow-list. NVD is queried per-ID.
keywordstringFree-text; pushed to NVD keywordSearch and re-checked client-side.
vendor / product / cpestringCase-insensitive substring filters.
cvssMinnumber 0–10 (decimals allowed)Minimum CVSS base score, e.g. 7.5. CVEs with no CVSS are excluded.
kevOnlybooleanfalseKeep only CISA-KEV-listed CVEs.
epssMinnumber 0–1Minimum EPSS probability.
epssPercentileMinnumber 0–1Minimum EPSS percentile.
publishedFrom / publishedToISO dateInclusive published-date range.
modifiedFrom / modifiedToISO dateInclusive last-modified-date range.
sinceISO dateOverride the automatic incremental cursor for one run.
includeRawbooleantrueInclude a compact raw object of upstream values for auditing.

Example — KEV compliance watch:

{ "mode": "incremental", "kevOnly": true, "maxItems": 500 }

Example — likely-exploited, high-severity Apache exposure:

{ "vendor": "apache", "cvssMin": 7, "epssPercentileMin": 0.95, "mode": "incremental" }

Incremental mode, cursors & durability

Incremental runs persist a composite keyset cursor (cursorDate, cveId) in a named key-value store (cyber-exposure-intelligence-state, key CEI_STATE). A named store is required because the default store is per-run on the Apify platform and would lose cross-run state.

  • Config-isolated cursor. The cursor is partitioned by a hash of the full filter set + cursor field. A narrow/filtered run therefore cannot advance the cursor used by a broader run — each distinct configuration drains independently. Over-partitioning is safe (a new partition just starts from a conservative lookback).
  • Oldest-first forward drain. When more matching CVEs exist than maxItems, the oldest matching records are emitted first and the cursor advances to the newest emitted record. The remainder is picked up on the next run — nothing is skipped, nothing is emitted twice.
  • Bounded recheck overlap. recheckWindowDays re-fetches already-committed CVEs just below the cursor and re-compares them, so genuine upstream NVD edits surface as changedFields. These re-checks never advance (or regress) the cursor.
  • Change tracking. firstSeen / lastSeen / changedFields are maintained per CVE across runs.

Deterministic output & dedup

CVEs are deduplicated by canonical cveId with an order-independent selector (newest lastModified wins, with documented tie-breaks), and the emitted rows are sorted by risk.score then a stable cveId order. The derived intelligence is deterministic: given the same upstream data, the risk synthesis, ordering, dedup winner and field values are reproducible regardless of fetch order.

Two fields are, by design, run-stamped and therefore not byte-identical across runs: provenance.fetchedAt and lastSeen (and firstSeen/changedFields, which are cross-run change-tracking outputs). Dataset pushes are append-only and not idempotent: the Apify dataset has no upsert/idempotency key, so a run that fails after pushing some rows and is retried will re-append those rows. Deduplicate downstream on recordId (== cveId), keeping the row with the newest provenance.fetchedAt.

Safety: partial failures never corrupt state

  • NVD (base) fails with zero records → the run throws and writes no cursor, so the platform retries and nothing is skipped.
  • KEV or EPSS enrichment fails but no filter depends on it → records are still emitted with that enrichment marked failed; the cursor advances normally (the CVE selection is unaffected). The last known-good enrichment snapshot is preserved: an optional outage never overwrites the stored KEV/EPSS/risk values with failure placeholders and never fabricates a changedFields entry for them, so the outage — and the subsequent recovery — do not produce spurious "changed" events. When the feed recovers, only a genuine change vs. the last good value is reported.
  • KEV or EPSS enrichment fails and a filter depends on it (kevOnly, epssMin, epssPercentileMin) → the run emits nothing for that dimension and holds the cursor, rather than misclassifying records. This is reported as enrichmentBlocked: true in the summary.
  • Write-before-state ordering. The durable cursor/state is committed last, only after every dataset record and the LAST_RUN_SUMMARY / OUTPUT records are written. Any failure before that final write leaves the cursor exactly where it was — a replay re-processes the same records and re-advances the same cursor. This guarantees no record is skipped; it does not de-duplicate the dataset, because a partial run's already-pushed rows are re-appended on retry (dedup downstream on recordId, as above).

Concurrency

Just before the final state write, the Actor re-reads and merges the persisted state (furthest cursor + unioned change-tracking entries). This narrows — but, because the Apify KV store has no compare-and-swap, cannot fully eliminate — a lost-update race between two runs of the same configuration that overlap in time.

Operational requirement — run at most one run per configuration at a time (maxConcurrency = 1). The Actor cannot self-enforce this (Apify has no built-in per-configuration mutex), so it is an operational contract:

  • Use a single non-overlapping schedule per configuration, and size maxNvdPages / lookbackDays so a run comfortably finishes before the next fires.
  • If you drive runs via the API/scheduler, set the run concurrency for this Actor to 1 (do not fan out concurrent runs that share the same filter set + cursorField, i.e. the same cursor partition).
  • Distinct configurations are already isolated by the partition key and may run concurrently safely — the constraint is only within one configuration.

Two runs of the same configuration that truly overlap can still lose one run's change-tracking updates (never causing a skipped record — the cursor only moves forward via maxCursor); keeping to one run per configuration avoids it entirely.

Each run also writes a LAST_RUN_SUMMARY (and OUTPUT) KV record with per-source status, counts by priority, KEV count, truncation/blocked flags and source attribution.


Pagination, NVD rate limits & backoff

  • NVD 2.0 caps a single query's date range at 120 days and returns up to 2000 results per page. This Actor splits [since, now] into ≤120-day sub-windows and pages each via startIndex / resultsPerPage until totalResults is drained or maxNvdPages is hit. Pagination advances by the number of rows the server actually returned, not the requested page size, so a server-side page-size cap (or a short non-final page) can never leave a gap. Every page is validated (a vulnerabilities array + a numeric totalResults consistent with the rows); a malformed or error body is treated as an uncaptured window (cursor held), never as a drained empty result.
  • Truncation is skip-safe. NVD does not return window pages sorted by the cursor field, so when a run is cut short by maxNvdPages the incremental cursor is advanced only as far as the end of the last fully-drained sub-window; records above that boundary are still emitted and simply suppressed as unchanged once the cursor catches up next run. This guarantees no CVE is skipped.
  • Rate limiting. NVD's public guidance is roughly 5 requests / 30 s without a key and 50 / 30 s with a key. The Actor enforces a minimum spacing between NVD requests (~6 s unkeyed, ~0.7 s keyed) and additionally honours 429 / Retry-After (delta-seconds and HTTP-date forms) with capped exponential backoff + jitter. Supplying nvdApiKey is strongly recommended for large runs.
  • Response bodies are size-capped and reads are timeout-bounded to protect the Actor from oversized or stalled upstream responses.

Limitations & conservative assumptions

  • EPSS refresh vs. the incremental cursor. EPSS scores change daily, but a CVE's NVD lastModified does not change when only its EPSS score does. Incremental mode keyed on lastModified therefore will not re-surface a CVE purely because its EPSS moved. To refresh EPSS/KEV across your whole corpus, run mode: full (or a targeted cveIds / kevOnly run) on a cadence. The recheckWindowDays overlap only covers CVEs near the cursor.
  • Enrichment applies after the per-source cap window. Enrichment-dependent filters (kevOnly, epssMin) are evaluated on up to a bounded number (3,000) of the oldest matching CVEs per run; a very large filtered window is drained across multiple runs (reported via truncated). Forward progress is guaranteed even with a zero-match prefix: because every candidate in that 3,000-record prefix is definitively classified when its enrichment succeeds, the cursor advances past the fully processed non-matches (a scan watermark), so a later run always reaches matches that lie beyond the budget — the backlog never stalls. The cursor is only ever held below records that were not fully processed (page-budget/mid-window NVD truncation, or a filter-required enrichment outage).
  • Recovering enrichment for CVEs committed during an optional outage. If a CVE is first seen while KEV/EPSS is down, it is stored with that enrichment failed and no score. It is re-enriched and corrected through one of three bounded paths: (1) the recheckWindowDays overlap re-checks CVEs just below the cursor every run; (2) any later NVD lastModified edit re-surfaces it above the cursor; and (3) a periodic mode: full run (recommended cadence: daily/weekly) re-enriches the whole lookback corpus. On correction the real KEV/EPSS data appears as a genuine changedFields event.
  • Very dense single windows. Because the cursor only advances past fully-drained sub-windows, a single ≤120-day window containing more than maxNvdPages × 2000 modified CVEs (e.g. during an NVD mass re-analysis event) cannot be fully drained in one run and will stall progress for that window. Raise maxNvdPages, supply an nvdApiKey, or narrow lookbackDays so each window fits the page budget. This is safe (no wrong data, no skips) but can slow a backfill.
  • CVSS selection. The best-available metric is chosen (v3.1 > v3.0 > v2, Primary > Secondary). For v2-only CVEs a severity label is derived from the base score, and CVSS scores originate from NVD (which may include ADP/CNA-provided values).
  • CPE-derived vendor/product are parsed heuristically from cpe:2.3 criteria and merged with KEV vendor/product; they are a convenience index, not an authoritative asset inventory.
  • KEV / EPSS coverage. KEV lists only the (relatively few) CVEs CISA has confirmed as exploited; EPSS only scores published CVEs. A missing entry with status: ok means genuinely absent, not an error.
  • The risk synthesis is a heuristic, not an official CISA/FIRST product; use it as one signal alongside your own context. All inputs are preserved so you can recompute your own scoring.
  • This Actor consumes public, unauthenticated feeds. No credentials are required (the optional NVD key only raises rate limits and is never logged).

Cost & pricing

Charge model. This Actor is billed on Apify platform usage — the standard pay-per-usage model: compute units (CUs) for actor runtime (1 CU = 1 GB of RAM × 1 hour) plus platform data-transfer/storage and, for residential proxy if you enable it, proxy traffic. This Actor uses no paid proxy by default (it calls public feeds directly) and holds only a small named state document plus your dataset, so compute time is the dominant cost, and NVD rate-limiting is what drives run time.

What a run costs (worked estimates). Default memory is 1 GB, so CU ≈ run-minutes ÷ 60. Using Apify's platform reference of roughly $0.4 / CU (check your current plan for the exact rate):

ScenarioRequests / pacingApprox. runtimeApprox. CUApprox. cost
Routine incremental, lookbackDays: 2, with nvdApiKey (~0.7 s/req)~3–8 NVD + 1 KEV + a few EPSS~20–60 s~0.006–0.017~$0.002–$0.007
Routine incremental, lookbackDays: 2, no key (~6 s/req)same~1–2 min~0.02–0.03~$0.008–$0.013
KEV-compliance watch (kevOnly, key)~5–15 NVD + KEV + EPSS batches~1–3 min~0.02–0.05~$0.008–$0.02
Full 30-day backfill, no keyup to maxNvdPages × 6 s~3–15 min~0.05–0.25~$0.02–$0.10
Large 1-year backfill, key, high maxNvdPagesmany pages~15–60 min~0.25–1.0~$0.10–$0.40

These are estimates, not a quote — actual cost depends on your Apify plan's CU rate, the run memory you configure, how many CVEs match, and NVD's current latency. Cost scales ≈ linearly with the number of NVD pages fetched, which is why an API key (10× the request rate) and a small lookbackDays cut cost the most.

How to keep it cheap:

  • Use an nvdApiKey — the single biggest lever (≈6 s → ≈0.7 s per request).
  • Prefer incremental mode with a modest lookbackDays; reserve mode: full for occasional corpus refreshes. Truncated work is safely resumed next run, so small, frequent incremental runs are cheaper and fresher than rare huge ones.
  • Size maxItems / maxNvdPages to what you actually consume.
  • KEV is a single (few-MB) download per run; EPSS is queried in batches of ≤100 CVE IDs (with an explicit limit so no scores are dropped), so enrichment cost scales with the number of matched CVEs, not the whole catalog.
  • includeRaw: false trims dataset storage/transfer if you do not need the audit fields.

Development

npm install
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run build # tsc -> dist/
npm test # node:test unit + adversarial + smoke tests
npm run smoke:local # offline, fixture-backed end-to-end run (CEI_SMOKE_FIXTURES=1)

The test suite covers caps/truncation, incremental cursor drain across runs, config-isolated cursors, source outages (NVD hard-fail, KEV/EPSS failures), enrichment-blocked cursor holds, partial enrichment, write-failure state safety, and concurrency/state merge. Fixtures under test/fixtures/ mirror the real NVD 2.0 / CISA KEV / FIRST EPSS response shapes.

Source attribution

Data is retrieved from and remains the property of its respective authorities:

Each record's provenance and canonicalUrl point back to the authoritative sources. Always verify against the official source before acting on a specific CVE.