# CISA KEV Priority Delta Monitor (`titan_coder/cisa-kev-priority-delta`) Actor

Informational monitor of the official CISA KEV catalog. Watch vendors, products or CVEs (or the whole catalog) and get diff rows only for entries genuinely new or changed since your last check -- no key, no account. A no-change run is free. Not a security assessment or compliance decision.

- **URL**: https://apify.com/titan\_coder/cisa-kev-priority-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.20 / 1,000 kev record new or changeds

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

## CISA KEV Priority Delta Monitor

Durable, informational monitor of the official CISA **Known Exploited Vulnerabilities (KEV)** catalog —
the free, public JSON feed the U.S. Cybersecurity and Infrastructure Security Agency publishes at
`https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json`. No API key, no
account, no proxy, no browser.

**This is a mirror-with-diff of a public government catalog, not a security assessment or a compliance
decision.** It does not evaluate your exposure, does not tell you whether a CVE applies to your systems,
and is not a substitute for a vulnerability management program. It tells you, reliably and cheaply, when
CISA's own catalog gains or changes an entry you asked to watch.

### Why

CISA already publishes the whole catalog for free. What it doesn't give you is a durable, per-organization
"what's new for me since I last looked" — you either poll the whole feed yourself and diff it client-side,
or you don't watch at all. This actor keeps that diff for you: a watchlist of vendors/products/CVEs (or the
whole catalog), a durable memory of every entry's content between runs, and a bill only for entries that
are genuinely new or have genuinely changed.

### How it works

1. Every run fetches the entire CISA KEV catalog in one request (it's one JSON file, no pagination).
2. The **first** run for a `monitorId` establishes a baseline: every entry's content hash is stored,
   nothing is billed or delivered — you start monitoring from "now", not from the catalog's entire history.
3. Every later run compares the fresh catalog against the stored hashes:
   - an entry whose CVE ID was never seen before is **new**;
   - an entry whose CVE ID was seen before but whose content (due date, ransomware flag, required action,
     notes, vendor/product, description) differs is **changed**;
   - anything else is skipped entirely — no row, no charge, no claim.
4. Your `watchlist` (vendor/product/CVE, or empty for the whole catalog) and `includeRansomware` filter are
   applied to that new/changed set. Matching rows are written to the dataset and billed once each.
5. A hard per-run cap (`maxResultsPerRun`) protects you from a single run billing an unbounded backlog if
   CISA adds hundreds of entries at once — anything over the cap is picked up cleanly on the next run,
   nothing is lost or double-billed.

### Input

```json
{
  "monitorId": "my-kev-watchlist",
  "watchlist": [
    { "vendorProject": "Microsoft" },
    { "cveId": "CVE-2021-44228" }
  ],
  "includeRansomware": false,
  "maxResultsPerRun": 200,
  "notifyOn": "new_alerts",
  "webhookUrl": "https://example.com/webhook"
}
```

- `watchlist` — 0-100 objects, each `{vendorProject?, product?, cveId?}`. All fields set inside ONE object
  must match (AND); objects themselves are OR'd. Leave empty (or omit) to watch every new/changed entry in
  the whole catalog.
- `includeRansomware` — when `true`, narrows every match down to entries CISA flags
  `knownRansomwareCampaignUse: Known`. When `false` (default), that flag is reported on every row but does
  not filter anything out.
- `maxResultsPerRun` — 1-2000, default 200.

Add or change the watchlist any time under the same `monitorId` — the durable hash index covers the whole
catalog regardless of what the watchlist currently says, so widening the watchlist later correctly compares
against real prior state instead of treating everything as brand new.

### Output

One row per new/changed KEV entry: `cveID`, `status` (`new`/`changed`), `vendorProject`, `product`,
`vulnerabilityName`, `dateAdded`, `dueDate`, `knownRansomwareCampaignUse` (bool), `requiredAction`, `notes`,
`shortDescription`, `sourceUrl` (link to the official CISA KEV catalog page). A run that finds nothing new
still writes an honest `run_summary` row to the default dataset (never silently empty).

### Billing

Pay-per-event: `kev-record-new-or-changed` — charged once per CVE per distinct content state (the same CVE
can be billed again later if CISA genuinely changes that record, but never twice for the same content). The
baseline run establishes history without charging. Failed/blocked runs (source fetch failed, run timed out)
are never charged.

#### Delivery guarantee: at-most-once (not exactly-once)

The right to write a row and to charge for it is granted by a single atomic primitive — one
`addRequest(uniqueKey)` into a dedicated, named claim-journal Request Queue
(`<prefix>-<monitorId>-claims`). Exactly one run ever wins that key. Claim requests are never deleted
and never handled: the queue is a permanent journal of irreversible attempts, not a work list.

- **You will never be charged twice for the same record state.** That is the guarantee.
- **It is not exactly-once.** If a run wins the claim and then dies before the row reaches the dataset
  (or before the charge completes), that change is *lost*: it closes as `dataset_unknown` /
  `charge_unknown` and is never re-delivered. We deliberately prefer losing a delivery over
  double-charging you.
- **Boundary of the guarantee:** it holds for as long as the named claim-journal queue exists. Anyone
  with account access can delete or re-create that queue through the Apify Console/API; a fresh journal
  starts empty, and previously delivered changes could then be delivered and billed again. That is an
  inherent limit of any durable storage, not a defect of the protocol.
- **Migration boundary:** the guarantee applies from the build that introduced the claim gate onward.
  Older builds of this actor must not keep running against the same `monitorId`. That same build also
  had to shorten the durable storage name prefix (the old one, the full actor name, could not fit
  Apify's 63-character storage-name limit together with a 40-character `monitorId`), so a monitor that
  ran on an older build starts from a fresh baseline once. A baseline is never charged.
- `coverage.claimJournalSize` reports the journal's size each run (best-effort; `null` if the queue's
  metadata could not be read, and the value lags a few seconds because Apify's `totalRequestCount` is
  eventually consistent). Use it to watch growth, not to make decisions.

### Honest limits

- We fetch CISA's feed as published; we do not second-guess or enrich it with our own risk scoring — that
  would misrepresent what this tool is (see the disclaimer above).
- If the feed is temporarily unavailable or its shape changes, the run reports that honestly
  (`sourceStatus`/`FAILED` with a reason) instead of silently returning zero results.
- `sourceUrl` points at CISA's official catalog page — CISA does not publish stable per-entry deep links, so
  we don't invent one.

Author: OmniCoder (https://t.me/OmniCoder)

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars).

## `watchlist` (type: `array`):

0-100 objects: {"vendorProject": "Microsoft"}, {"product": "Windows"}, {"cveId": "CVE-2024-12345"}, or any combination inside one object (all set fields must match). Leave empty to watch every new or changed entry in the whole catalog.

## `includeRansomware` (type: `boolean`):

When enabled, narrows every match (watchlist or whole catalog) to entries CISA flags with knownRansomwareCampaignUse = Known. When disabled (default), ransomware status is reported but does not filter results.

## `maxResultsPerRun` (type: `integer`):

Cap on new/changed rows delivered and billed in a single run (1-2000). Extra matches are picked up on the next run — nothing is lost.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when new/changed paid entries were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (paid) new/changed KEV entries as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-kev-watchlist",
  "watchlist": [
    {
      "vendorProject": "Microsoft"
    },
    {
      "cveId": "CVE-2021-44228"
    }
  ],
  "includeRansomware": false,
  "maxResultsPerRun": 200,
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

## `results` (type: `string`):

Every row this run produced. Key fields: cveID, status (new/changed), vendorProject, product, dateAdded, dueDate, knownRansomwareCampaignUse, requiredAction, notes, sourceUrl.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: catalog size, watchlist matches, delta found/delivered/billed, honest source status. Enough to reconcile every charge against every row.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-kev-watchlist",
    "watchlist": [
        {
            "vendorProject": "Microsoft"
        },
        {
            "cveId": "CVE-2021-44228"
        }
    ],
    "includeRansomware": false,
    "maxResultsPerRun": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/cisa-kev-priority-delta").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 = {
    "monitorId": "my-kev-watchlist",
    "watchlist": [
        { "vendorProject": "Microsoft" },
        { "cveId": "CVE-2021-44228" },
    ],
    "includeRansomware": False,
    "maxResultsPerRun": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/cisa-kev-priority-delta").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 '{
  "monitorId": "my-kev-watchlist",
  "watchlist": [
    {
      "vendorProject": "Microsoft"
    },
    {
      "cveId": "CVE-2021-44228"
    }
  ],
  "includeRansomware": false,
  "maxResultsPerRun": 200
}' |
apify call titan_coder/cisa-kev-priority-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/cisa-kev-priority-delta"
        }
    }
}
```

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/kyII7LKMfazf1cnR0/builds/AWm9A56aldjJfrgxh/openapi.json
