Getting Started with CVE Prioritization Using VulnCheck Community Data

Use VulnCheck Community data to prioritize CVEs with a starter Python script.

Your scanner handed you hundreds of CVEs. NVD tells you severity. CISA KEV tells you something was exploited. Once, somewhere, at some point. The challenge isn't finding CVEs. It's knowing which signals matter for your team's current priorities, whether that's active exploitation, asset exposure, compliance scope, or threat actor relevance.

VulnCheck Community data gives you a richer set of signals to work with, via two bulk ZIP downloads. We'll walk through how to use them in Python to show what's happening under the hood.


Get the Data

Register at console.vulncheck.com (Community tier). Full setup (account creation, API token generation, and your first bulk download) is covered in the Getting Started guide. For a broader picture of what Community gives you, see Top 5 Reasons to Use VulnCheck Community.

Two feeds are relevant here:

  • vulncheck-kev.zip — VulnCheck's enriched KEV dataset. Schema and field reference: vulncheck-kev schema
  • nist-nvd2.zip — NIST NVD2 data in JSON format, containing CVSS vectors, CWE classifications, and vulnerability metadata. Useful as supplementary context; your scanner likely already surfaces this data

Signals in the Data

A KEV entry carries threat context beyond confirming exploitation. Which signals matter depends on your team's priorities at a given time. Here's a real entry to illustrate:

{
  "cve": ["CVE-2022-22954"],

  "knownRansomwareCampaignUse": "Known",

  "vulncheck_xdb": [
    {
      "exploit_type": "initial-access",
      "clone_ssh_url": "git@github.com:sherlocksecurity/VMware-CVE-2022-22954.git",
      "date_added": "2022-04-11T13:59:13Z"
    }
  ],

  "vulncheck_reported_exploitation": [
    {
      "url": "https://www.vmware.com/security/advisories/VMSA-2022-0011.html",
      "date_added": "2022-04-06T00:00:00Z"
    },
    {
      "url": "https://www.cisa.gov/.../known_exploited_vulnerabilities.json",
      "date_added": "2022-04-14T00:00:00Z"
    },
    {
      "url": "https://blog.morphisec.com/vmware-identity-manager-attack-backdoor",
      "date_added": "2022-04-25T00:00:00Z"
    }
  ],

  "reported_exploited_by_vulncheck_canaries": true,

  "date_added": "2022-04-06T00:00:00Z",
  "cisa_date_added": "2022-04-14T00:00:00Z"
}

Active Exploitation Fields

Presence in VulnCheck KEV is itself a signal. The catalog tracks confirmed exploitation evidence beyond what CISA publishes, and typically surfaces it earlier.

vulncheck_reported_exploitation[] — timestamped list of every source that has publicly reported exploitation. Source count and recency separate an active campaign from a historical record.

reported_exploited_by_vulncheck_canariestrue when VulnCheck canaries observe live attack traffic targeting this CVE in actual network traffic.

knownRansomwareCampaignUse"Known" means ransomware groups have actively adopted this CVE.

date_added vs cisa_date_added — the gap between when VulnCheck first tracked exploitation and when CISA published it. In the example above: VulnCheck April 6, public exploit code April 11, CISA April 14. The wider this gap, the more exposure a CISA-only workflow carries.


Exploit Artifact Fields

vulncheck_xdb[] — VulnCheck's exploit database: each artifact classified by exploit_type with a clone_ssh_url to the repository. Neither NVD nor CISA provides this classification. Any entry indicates exploit code exists, ranging from proof-of-concept to fully operational. For type definitions see the exploit type classification reference.


Other signals in the schema serve different priority lenses: SSVC for decision-framework teams, EPSS for probability-based scoring, ICS/OT and IoT tags for sector-specific risk. Botnet and threat actor context surfaces through the exploitation report URLs in vulncheck_reported_exploitation[] rather than as structured fields.


Scoring Your CVE List

All you need is a list of CVE IDs. It can come from a vulnerability scanner, a patch management tool, a SIEM alert, or even a manually curated list. The scoring process is the same regardless of where the list came from.

Load the KEV zip once into memory indexed by CVE ID. For each CVE in your list, look it up and score it against the signals that matter to your team.

Output a structured record per CVE with LLM-readable field names. Each key-value pair reads as a factual statement, which works for teams acting on the data directly and teams passing it to an LLM.

import json, zipfile

with zipfile.ZipFile("vulncheck-kev.zip") as z:
    raw = json.loads(z.read("vulncheck_known_exploited_vulnerabilities.json"))
kev = {cve_id: e for e in raw for cve_id in e.get("cve", [])}

results = []
for cve_id in your_cve_list:
    k = kev.get(cve_id)
    if not k:
        continue

    record = {
        "cve": cve_id,
        "used_in_active_ransomware_campaign":    k.get("knownRansomwareCampaignUse") == "Known",
        "live_exploitation_observed_in_honeypot": k.get("reported_exploited_by_vulncheck_canaries", False),
        "public_exploit_no_auth_required":  any(x["exploit_type"] == "initial-access"
                                                      for x in k.get("vulncheck_xdb", [])),
    }
    # only include CVEs with at least one signal active
    if any(record[f] for f in record if f != "cve"):
        results.append(record)

print(json.dumps(results, indent=2))

Example output:

[
  {
    "cve": "CVE-2022-22954",
    "used_in_active_ransomware_campaign": true,
    "live_exploitation_observed_in_honeypot": true,
    "public_exploit_no_auth_required": true
  },
  {
    "cve": "CVE-2024-12345",
    "used_in_active_ransomware_campaign": false,
    "live_exploitation_observed_in_honeypot": false,
    "public_exploit_no_auth_required": true
  }
]

Which signals to surface (and how to weight them) depends on the decision you're trying to support. A CISO needs ransomware linkage for executive reporting. A security analyst needs active exploitation signals for triage. A patch team needs exploit availability for remediation planning.

Without AI — Rank and Act Directly

Taking the security analyst view as an example: the question is what to patch first. Not what is theoretically severe, but what is being actively exploited right now. Counting the true values gives a simple score to sort on:

ranked = sorted(results, key=lambda r: sum(v for k, v in r.items() if k != "cve"), reverse=True)

Replace the equal-weight count with a scoring function that reflects your actual priorities. The signals are the raw material; the scoring function is yours to define. Pipe the output into a spreadsheet, a ticket, or directly into your LLM.


With AI — Pass the Ranked Output to an LLM

Take the ranked JSON from the step above and hand it to whatever LLM your team has access to. The field names are written to be LLM-readable: each key-value pair reads as a factual statement, so the model reasons about the signal directly without needing prompt scaffolding.

Prompt:

You are a security analyst. The following CVEs were detected by our scanner and enriched with vulnerability intelligence. Based on the signals present, help prioritize them for our team and summarize the risk for each in one sentence.

[paste ranked JSON here]


Go Further

The signals shown here are a starting point. A fuller prioritization picture draws on additional context: SSVC decision trees, EPSS exploitation probability, CVSS vectors, ICS/OT and IoT exposure tags, CAPEC attack patterns, structured botnet and threat actor attribution. Some of these are available within the VulnCheck dataset; others require going beyond the Community tier. For the prioritization framework and thinking behind these signals, see Vulnerability Prioritization.