# CVE active exploitation check (KEV + EUVD) (`garonne/cve-exploited-check`) Actor

For each CVE ID, answers one question: is it actively exploited? Cross-references CISA's Known Exploited Vulnerabilities catalog and ENISA's EUVD in memory, downloaded once per run. Pay-per-event: one cve-lookup charge per valid CVE, including a verified negative.

- **URL**: https://apify.com/garonne/cve-exploited-check.md
- **Developed by:** [matheo daney](https://apify.com/garonne) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 cve lookups

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## CVE active exploitation check — CISA KEV + ENISA EUVD, one answer per CVE

**For every CVE ID you provide, this Actor answers exactly one question: is it actively exploited?** It cross-references CISA's Known Exploited Vulnerabilities (KEV) catalog and ENISA's EU Vulnerability Database (EUVD) exploited-vulnerabilities list, downloaded fresh **once per run** and checked entirely in memory — not one API call per CVE. That is what keeps this fast and cheap even on a batch of hundreds of IDs. Built for AI agents and vulnerability-management pipelines that need a single, source-backed yes/no, not a CVSS severity score.

### Why this matters now: the EU Cyber Resilience Act

Since **11 September 2026**, the EU Cyber Resilience Act (CRA) requires manufacturers of products with digital elements to notify **actively exploited vulnerabilities** within **24 hours** of becoming aware of them (Article 14). The trigger is not a CVE's severity score — a CVSS 9.8 with no known exploitation triggers nothing under Article 14 — it is confirmed active exploitation. This Actor is built to surface exactly that signal, quickly, for a list of CVE IDs. (This is background context, not legal advice — see **Legal context** below.)

### Use this Actor when…

- Your CRA Article 14 monitoring needs to check, on a schedule, whether any CVE affecting your products has just entered a known-exploited catalog.
- An AI agent is triaging a vulnerability scan or SBOM report and needs to separate "exploited right now" from "high CVSS but no known exploitation" before deciding what to escalate.
- You maintain a vulnerability-management pipeline and want a fast, in-memory bulk check across hundreds of CVE IDs instead of one API call each.
- You need this as a **tool an AI agent can call directly** — via the Apify API, the [Apify MCP server](https://docs.apify.com/integrations/mcp), or an [n8n](https://docs.apify.com/platform/integrations/n8n) workflow.

This Actor does **not** compute or guess exploitation status. Every answer comes from two official, public, no-API-key sources: CISA's KEV catalog (United States) and ENISA's EUVD (European Union). See **Limits & data sources** below for exact citations, and **Legal context** for why "not in either catalog" is not the same thing as "safe."

### How this Actor works (and why it's cheap at scale)

Unlike a per-identifier lookup Actor, this one downloads both catalogs **once at the start of the run** — CISA's KEV JSON (a single file, ~1,700 entries) and ENISA's EUVD exploited-vulnerabilities list (paginated, ~1,700 entries at ~100/page) — builds two in-memory lookup tables, and then answers every CVE in your input list with a plain table lookup. Cost and run time are dominated by the fixed catalog download, not by how many CVEs you check. See [`src/kev.js`](src/kev.js) and [`src/euvd.js`](src/euvd.js) for the exact endpoints and how each catalog is normalized, and [`src/main.js`](src/main.js) for the overall flow.

The catalogs are **never cached between runs**, even though it would be technically possible — see **Limits & data sources** for why that would be the wrong trade-off for this specific use case.

### Input

| Field     | Type                               | Default      | Description                                                                                                                                                                                               |
| --------- | ---------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cveIds`  | array of strings                   | *(required)* | One CVE ID per array item, e.g. `"CVE-2024-3400"`. Official syntax: `CVE-` + 4-digit year + 4-to-19-digit number. Anything else is returned as an error record (no charge), never sent to either catalog. |
| `sources` | string (`kev` | `euvd` | `both`) | `both`       | Which catalog(s) to check. See the field's in-console description for the important note about KEV/EUVD overlap (also covered in **Limits & data sources** below).                                        |

Full field-level descriptions are in [`.actor/input_schema.json`](.actor/input_schema.json).

#### Example input

```json
{
    "cveIds": ["CVE-2024-3400", "CVE-2021-44228", "CVE-2018-1000001", "CVE-2020-25705", "CVE-2026-12"],
    "sources": "both"
}
```

### Output example

Real output from a local test run (2026-09-04). A CVE confirmed exploited by both catalogs (Palo Alto PAN-OS GlobalProtect, CVE-2024-3400):

```json
{
    "input": "CVE-2024-3400",
    "cveId": "CVE-2024-3400",
    "valid": true,
    "exploited": true,
    "sources": ["KEV", "EUVD"],
    "catalogsChecked": ["KEV", "EUVD"],
    "dateAdded": "2024-04-12",
    "dueDate": "2024-04-19",
    "vendorProject": "Palo Alto Networks",
    "product": "PAN-OS",
    "checkedAt": "2026-09-04T17:08:39.837Z",
    "charged": true,
    "error": null
}
```

A real, valid CVE that is **not** in either catalog — still a genuine, billable answer, not an error:

```json
{
    "input": "CVE-2018-1000001",
    "cveId": "CVE-2018-1000001",
    "valid": true,
    "exploited": false,
    "sources": [],
    "catalogsChecked": ["KEV", "EUVD"],
    "dateAdded": null,
    "dueDate": null,
    "vendorProject": null,
    "product": null,
    "checkedAt": "2026-09-04T17:08:39.850Z",
    "charged": true,
    "error": null
}
```

A syntactically invalid CVE ID — never charged, never sent to either catalog:

```json
{
    "input": "CVE-2026-12",
    "cveId": null,
    "valid": false,
    "exploited": null,
    "sources": [],
    "catalogsChecked": [],
    "dateAdded": null,
    "dueDate": null,
    "vendorProject": null,
    "product": null,
    "checkedAt": null,
    "charged": false,
    "error": "\"CVE-2026-12\" does not match the official CVE ID syntax \"CVE-YYYY-N\" (a 4-digit year followed by 4 to 19 digits)."
}
```

Every dataset item has exactly this set of keys, whether the check succeeded, came back negative, or failed — a `null` value means "not applicable", never "unknown due to an error" (that's what `error` is for).

| Field             | Meaning                                                                                                                                                                                                                                                                            |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `input`           | The raw CVE ID string as given.                                                                                                                                                                                                                                                    |
| `cveId`           | The normalized (uppercased) CVE ID, or `null` if `valid` is `false`.                                                                                                                                                                                                               |
| `valid`           | `true` if the input matches the official CVE ID syntax. `false` means it was never looked up anywhere.                                                                                                                                                                             |
| `exploited`       | `true`/`false` from a completed check, or `null` if the check could not be completed this run (see `error`) or the input was invalid. **`false` means "not found in the catalog(s) actually consulted at the time of the check" — never "confirmed safe."** See **Legal context**. |
| `sources`         | Which catalog(s) confirmed exploitation: `"KEV"`, `"EUVD"`, both, or `[]` if neither did (or the CVE is invalid).                                                                                                                                                                  |
| `catalogsChecked` | Which catalog(s) were **actually** consulted for this record this run — reflects the `sources` input option minus any catalog that failed to download. Always read this alongside `exploited: false` to know exactly what was checked.                                             |
| `dateAdded`       | Date (`YYYY-MM-DD`) this CVE was added to whichever matching catalog added it earliest. `null` if not exploited or invalid.                                                                                                                                                        |
| `dueDate`         | CISA KEV's own remediation due date. **Only ever populated from KEV** — always `null` if KEV was not a matching source, even if EUVD matched.                                                                                                                                      |
| `vendorProject`   | Vendor/project name, if available (KEV's naming is preferred when both sources matched).                                                                                                                                                                                           |
| `product`         | Product name, if available.                                                                                                                                                                                                                                                        |
| `checkedAt`       | ISO 8601 timestamp of this run's catalog download, for this record. `null` if invalid.                                                                                                                                                                                             |
| `charged`         | `true` if this record triggered a `cve-lookup` charge.                                                                                                                                                                                                                             |
| `error`           | Explanation if the check could not be completed (implies `charged: false`), otherwise `null`.                                                                                                                                                                                      |

You can download the dataset in JSON, CSV, Excel, or several other formats from the Apify Console or the API. The Console's *Output* tab shows a pre-labeled table view ([`.actor/dataset_schema.json`](.actor/dataset_schema.json)).

### Pricing

This Actor uses Apify's **pay-per-event (PPE)** pricing model — see [Apify's PPE documentation](https://docs.apify.com/sdk/js/docs/concepts/pay-per-event) for how the mechanism itself works. There is a single billable event:

- **`cve-lookup`** — charged once per CVE ID for which the check **genuinely completed** against every catalog you selected: either it was found in at least one of them (`exploited: true`), or every selected catalog was successfully consulted and found nothing (`exploited: false`). A confirmed negative is billed the same as a confirmed positive — the client asked "is this exploited according to the catalog(s) I selected" and got a complete, genuine answer either way.

**You are never charged for:**

- a syntactically malformed CVE ID (rejected before any network call is made, per the [CVE Program's own ID syntax](https://raw.githubusercontent.com/CVEProject/cve-schema/master/schema/CVE_Record_Format.json) — see [`src/identifier.js`](src/identifier.js));
- a run where a catalog you selected could not be downloaded even after automatic retries — see `catalogsChecked`/`error` on the affected records, which say exactly what could and could not be checked.

Target price: **$0.005 per `cve-lookup`** ($5 per 1,000 checks) — configured on the Actor's *Pricing* page in Apify Console, not in this code. In the code, the event is declared with the `Actor.pushData(item, 'cve-lookup')` shortcut for `Actor.charge()` (see [`src/main.js`](src/main.js) and [`src/record.js`](src/record.js) for exactly where the charge/no-charge decision is made).

### Limits & data sources

**CISA KEV** — `GET https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json`, linked as the "JSON" download from the catalog's own page, `https://www.cisa.gov/known-exploited-vulnerabilities-catalog` (fetched and read directly 2026-09-04). No API key. 1,694–1,705 entries at verification time (the catalog changes as CISA adds CVEs). Fields used: `cveID`, `vendorProject`, `product`, `vulnerabilityName`, `dateAdded`, `dueDate`, `knownRansomwareCampaignUse`.

**License**: **Creative Commons Zero (CC0) 1.0** — confirmed 2026-09-04 at `https://www.cisa.gov/sites/default/files/licenses/kev/license.txt` ("You may use this data in any legal manner"). Full commercial reuse, no attribution legally required.

**Update cadence**: CISA does not publish a fixed schedule for this feed anywhere this Actor's research could find (checked the catalog page and the directive that established it). The response's `dateReleased` field carries sub-second precision, consistent with CISA regenerating the file as entries are confirmed (event-driven), not on a daily batch. This is why this Actor re-downloads it fresh on every run rather than caching it — see **How this Actor works** above.

**Governing directive**: the 2021 directive that established KEV, BOD 22-01, was **revoked** and superseded by **BOD 26-04** — confirmed 2026-09-04 at CISA's own archived copy of BOD 22-01 ("This Directive has been revoked. It is superseded by BOD 26-04."). This affects which U.S. federal directive currently governs KEV, not the catalog's own data format, license, or public URL.

**ENISA EUVD** — base URL `https://euvdservices.enisa.europa.eu`, documented (with live curl examples) at `https://euvd.enisa.europa.eu/apidoc` (read directly 2026-09-04): "All endpoints are GET, require no authentication, no custom headers, and no request body." This Actor uses `GET /api/search?exploited=true&size=100&page=N`, paginated (`size` is capped at 100 server-side regardless of the value requested, verified live). **Not** `/api/exploitedvulnerabilities` — that endpoint is explicitly documented as "Show latest exploited vulnerabilities", capped at a **maximum of 8 records** (a homepage teaser, confirmed live by calling it directly), and would silently miss almost every real match. No documented rate limit was found (checked `/apidoc` and `/faq`); this Actor paces its own paginated requests with an ordinary retry/backoff regardless.

**EUVD is established under the NIS2 Directive, not the CRA** — confirmed 2026-09-04 at `https://euvd.enisa.europa.eu/faq`, question 8: *"The Single Reporting Platform (SRP) established under the Cyber Resilience Act (CRA) is different from the NIS2 Directive-established EUVD... While the objective of the EUVD is to make information on publicly known vulnerabilities accessible, the SRP focuses on the mandatory reporting of actively exploited vulnerabilities... not yet publicly documented."* **This matters directly for the CRA use case this Actor is built around: checking EUVD (or KEV) is a monitoring/detection input that can help you notice a trigger — it is not the CRA Article 14 reporting channel itself, and a negative result here is never proof that no Article 14 obligation exists.** See **Legal context** below.

**⚠️ KEV and EUVD are not independent signals today — verified, not assumed.** This session compared the full set of CVE IDs returned by paginating `/api/search?exploited=true` against ENISA's own separate consolidated dump endpoint (`/api/kev/dump`, documented as "Consolidated Known Exploited Vulnerabilities from CISA KEV and ENISA EU KEV sources") on 2026-09-04: **the two returned the exact same 1,705 CVE IDs, zero difference either way.** Breaking that dump down by its own `sources` tags: of 1,705 total, **1,655 are CISA-KEV-only, 40 are in both, and only 10 are EU-KEV-exclusive** (not in CISA's KEV at all). In other words, EUVD's own "exploited" flag is, today, almost entirely a republication of CISA's KEV list plus a small EU-specific addition — **selecting `"both"` sources does not currently give you two independent confirmations of the same fact for the large majority of matches.** This Actor still queries EUVD directly and reports it as its own source (that is what a client checking euvd.enisa.europa.eu would themselves see), and still reports `sources: ["KEV", "EUVD"]` honestly when both flag a CVE — but this note exists so a client relying on this output for a compliance judgment does not read "confirmed by 2 sources" as more independent corroboration than it currently is. This is a snapshot of ENISA's data as of the verification date, not a permanent architectural fact — it may become less true as ENISA's own EU KEV grows.

**No caching between runs, by design**: both catalogs are downloaded fresh on every run. Given the whole point of this Actor is to catch a CVE that was *just* added to a catalog, serving a previous run's cached data risks the exact failure mode this Actor exists to prevent.

### Legal context

This Actor exists because of the EU Cyber Resilience Act's Article 14 (in force since 11 September 2026), which requires manufacturers to notify ENISA/CSIRTs of an **actively exploited** vulnerability in a product with digital elements within **24 hours** of becoming aware of it. This section explains what this Actor's output does and does not tell you about that obligation. **It is not legal advice; consult qualified counsel for an actual CRA compliance determination.**

- **A `true` result is a real, sourced signal** — the CVE appears in a catalog a national authority (CISA) or an EU agency (ENISA) has itself published as actively exploited, as of the `checkedAt` timestamp.
- **A `false` result means "not found in the catalog(s) checked at this moment" — it does not mean the vulnerability is not being exploited.** Neither KEV nor EUVD claims to be exhaustive: KEV lists what CISA has confirmed and chosen to add (a US federal prioritization tool, not a universal registry); EUVD aggregates from CVE/CSIRT/vendor sources with its own inclusion process. A vulnerability can be under active, even widespread exploitation and simply not be in either catalog yet, or ever, if it stays outside their intake criteria. **This Actor never states that a CVE "is not exploited" — only that it "does not appear in the catalog(s) consulted at the time of verification."**
- **Checking this Actor is not the same thing as fulfilling an Article 14 report.** As EUVD's own FAQ makes clear (quoted in **Limits & data sources** above), the CRA's actual reporting channel is the Single Reporting Platform (SRP), a separate, non-public mechanism for manufacturers — distinct from the public EUVD this Actor queries.
- **Use this as an input to a monitoring process, not as the final word.** A `true` result is strong grounds to start your organization's 24-hour clock; a `false` result should not, by itself, be treated as clearance — it should be one signal among others (vendor advisories, your own incident data, threat intelligence) in a broader exploitation-monitoring process.

### Integrations (API, MCP, n8n)

- **Apify API** — run this Actor from any HTTP client via `POST https://api.apify.com/v2/acts/<your-username>~cve-exploited-check/run-sync-get-dataset-items` (or the async run + get-dataset-items pair) with your API token. See the Actor's *API* tab in Apify Console for ready-to-copy snippets (cURL, Python, JS).
- **MCP (Model Context Protocol)** — expose this Actor as a tool to any MCP-compatible AI agent via the [Apify MCP server](https://docs.apify.com/integrations/mcp) (`https://mcp.apify.com`, or `@apify/actors-mcp-server` for local/self-hosted use). An agent can call it directly with a list of CVE IDs pulled from a scan report or SBOM.
- **n8n** — use the official [Apify node for n8n](https://docs.apify.com/platform/integrations/n8n) (`@apify/n8n-nodes-apify`) to run this Actor on a schedule against your product's tracked CVEs, and branch a workflow on `exploited: true` to kick off your CRA Article 14 escalation process.

# Actor input Schema

## `cveIds` (type: `array`):

One CVE identifier per array item, e.g. "CVE-2026-58400". The official syntax is "CVE-" + a 4-digit year + a 4-to-19-digit number (source: the CVE Program's own JSON schema, CVEProject/cve-schema on GitHub). Case and surrounding whitespace are normalized automatically. Any item that does not match this syntax is returned as an error record (no charge) — it is never sent to either catalog.

## `sources` (type: `string`):

Which catalog(s) to cross-reference each CVE against. "kev": CISA's Known Exploited Vulnerabilities catalog only (United States, cisa.gov) — a KEV-specific due date, when present, is only ever populated from this source. "euvd": ENISA's EU Vulnerability Database exploited-vulnerabilities list only (European Union, euvd.enisa.europa.eu). "both" (default): consult both and report whichever confirm exploitation. IMPORTANT, verified 2026-09-04: at the time of writing, EUVD's own 'exploited' flag has 100% overlapping CVE coverage with the consolidated KEV data ENISA itself re-publishes (only ~50 EU-specific entries exist beyond CISA's own KEV list) — see README "Limits & data sources" before assuming the two sources are fully independent confirmations.

## Actor input object example

```json
{
  "cveIds": [
    "CVE-2024-3400",
    "CVE-2021-44228",
    "CVE-2026-58400"
  ],
  "sources": "both"
}
```

# Actor output Schema

## `cveRecords` (type: `string`):

URL of the run's default dataset items (JSON array of CVE exploitation records, one per input CVE ID).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "cveIds": [
        "CVE-2024-3400",
        "CVE-2021-44228",
        "CVE-2026-58400"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("garonne/cve-exploited-check").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "cveIds": [
        "CVE-2024-3400",
        "CVE-2021-44228",
        "CVE-2026-58400",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("garonne/cve-exploited-check").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "cveIds": [
    "CVE-2024-3400",
    "CVE-2021-44228",
    "CVE-2026-58400"
  ]
}' |
apify call garonne/cve-exploited-check --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,garonne/cve-exploited-check"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DnqMNu7BZscBpaVqa/builds/nuvPcwIwVJ3n6vK24/openapi.json
