Knowledge Graphs for Vulnerability Prioritisation: Surviving the AI Patch Tsunami
Microsoft's June 2026 Patch Tuesday delivered a stark message: the era of human-scale vulnerability management is over. With a staggering 206 Common Vulnerabilities and Exposures (CVEs) addressed, including 6 zero-days and 33 critical flaws, security teams globally faced the largest single patch release on record. The sheer volume of vulnerabilities now entering the ecosystem demands a radical shift in how organisations prioritise and respond.
The Problem: When CVSS Scores Fail in Isolation
For years, the Common Vulnerability Scoring System (CVSS) has been the bedrock of vulnerability prioritisation. It provides a standardised, numerical representation of a vulnerability's severity. However, CVSS, in isolation, tells only part of the story. It describes the potential impact of a flaw, not its actual risk within a specific operational context.
Two additional data sources have emerged to fill the gap, yet both remain underutilised. The Exploit Prediction Scoring System (EPSS) uses real-world exploit intelligence to estimate the probability that a vulnerability will be exploited in the wild within 30 days. EPSS scores range from 0 to 1 (or 0% to 100%). A CVE with an EPSS score above 0.9 is virtually certain to be weaponised; one below 0.01 is rarely observed in active attacks. The CISA Known Exploited Vulnerabilities (KEV) catalogue tracks vulnerabilities that have been confirmed as actively exploited, providing a binary signal: this flaw is being used in the wild right now.
Even armed with CVSS, EPSS, and CISA KEV, security teams still face a combinatorial explosion. A typical enterprise runs hundreds of applications, each depending on thousands of open-source and commercial components. A single critical CVE in Log4j, OpenSSL, or curl can ripple through 80% of an organisation's software estate. Patching everything with a CVSS ≥ 7.0 is physically impossible inside a single patch window. Teams need to answer three questions for every CVE:
- Reachability — Is the vulnerable component actually deployed and reachable in my environment?
- Blast radius — What systems and data would be affected if this vulnerability were exploited?
- Exploitability — Is there active exploitation, and how likely is it?
CVSS cannot answer these questions. A knowledge graph can.
The Graph Approach: Modelling Your Infrastructure for True Risk
To move beyond the limitations of isolated CVSS scores, organisations must adopt a systemic view of their infrastructure. This is where knowledge graphs excel. By modelling your entire software landscape as a property graph, you can represent the intricate relationships between components, services, and data flows.
In this model:
- Nodes represent entities like applications, microservices, libraries, operating systems, network devices, data stores, and even individual code modules.
- Edges define the relationships between these entities:
DEPENDS_ON,EXPOSES,CONNECTS_TO,HOSTS,RUNS_ON,ACCESSES,STORES_DATA, etc.
This interconnected web allows for a dynamic calculation of "blast radius" — the full extent of systems and data reachable from a given vulnerability. A high-severity CVE in a rarely used, isolated internal component might have a tiny blast radius, while a moderate-severity flaw in a core, internet-facing library could expose your entire critical path.
Schema Design for the Vulnerability Knowledge Graph
Before querying, you need a schema. The following Neo4j constraints and indexes enforce data integrity and enable performant traversal:
// Uniqueness constraints — every entity must be identifiable
CREATE CONSTRAINT IF NOT EXISTS FOR (c:CVE) REQUIRE c.id IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (a:Application) REQUIRE a.name IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (h:Host) REQUIRE h.hostname IS UNIQUE;
// Indexes for fast filtering on commonly queried properties
CREATE INDEX IF NOT EXISTS FOR (c:CVE) ON (c.cvssScore);
CREATE INDEX IF NOT EXISTS FOR (c:CVE) ON (c.epssScore);
CREATE INDEX IF NOT EXISTS FOR (c:CVE) ON (c.kevPublished);
CREATE INDEX IF NOT EXISTS FOR (a:Application) ON (a.businessCriticality);
CREATE INDEX IF NOT EXISTS FOR (a:Application) ON (a.isInternetFacing);
The ontology defines four core node labels:
| Label | Purpose | Key Properties |
|---|---|---|
CVE | A known vulnerability identifier | id, cvssScore, epssScore, kevPublished, description, publishedDate |
Component | A software library, package, or OS | name, version, type, language, license |
Application | A business application or service | name, businessCriticality, isInternetFacing, handlesSensitiveData, owner |
Host | A physical or virtual machine | hostname, ipAddress, environment, region, os |
And the edges that connect them:
| Relationship | From | To | Semantics |
|---|---|---|---|
IMPACTS | CVE | Component | This CVE affects this component version |
DEPENDS_ON | Application | Component | Application uses this component (directly or transitively) |
RUNS_ON | Application | Host | Application is deployed on this host |
EXPOSES | Host | Application | Host exposes this application to the network |
MITIGATED_BY | CVE | Component | A compensating control or patch exists |
CONNECTS_TO | Host | Host | Network connectivity between hosts |
This schema captures the minimum viable graph for vulnerability prioritisation. In production, you would extend it with additional labels for network segments, data classifications, and business processes.
Technical Deep-Dive: Quantifying Blast Radius with Graph Traversal
Consider a scenario where a newly disclosed CVE impacts a specific software component or library. With a knowledge graph, you can quickly identify all applications and services that directly or indirectly depend on this vulnerable component, and critically, whether those dependent services are exposed or handle sensitive data.
Here is a Cypher query example for Neo4j, demonstrating how to find all applications that depend on a component identified as vulnerable:
MATCH (v:Vulnerability {cveId: 'CVE-2026-XXXX'})
WHERE v.isExploitable = TRUE -- Only consider actively exploitable vulnerabilities
MATCH (v)-[:IMPACTS]->(comp:Component)
WITH comp
MATCH (app:Application)-[:DEPENDS_ON*1..5]->(comp) -- Traverse up to 5 levels of dependency
WHERE app.isInternetFacing = TRUE OR app.handlesSensitiveData = TRUE
RETURN DISTINCT app.name AS VulnerableApplication,
app.businessCriticality AS BusinessCriticality,
collect(DISTINCT comp.name) AS ImpactedComponents
ORDER BY app.businessCriticality DESC
This query does more than just list dependencies. It:
- Filters for actively exploitable vulnerabilities.
- Identifies the directly impacted component.
- Traverses the dependency graph (
DEPENDS_ON*1..5) to find all applications relying on that component, up to five levels deep. - Crucially, it prioritises applications that are internet-facing or handle sensitive data, and orders them by business criticality.
Automating Prioritisation with Python
For continuous vulnerability monitoring, automate the prioritisation pipeline:
from neo4j import GraphDatabase
from dataclasses import dataclass, field
@dataclass
class PrioritisedVulnerability:
cve_id: str
cvss_score: float
blast_radius_score: float
affected_apps: list[str]
priority: str
def prioritise_vulnerabilities(driver) -> list[PrioritisedVulnerability]:
"""Score and rank vulnerabilities by contextual risk."""
with driver.session() as session:
results = session.run("""
MATCH (v:Vulnerability)-[:IMPACTS]->(comp:Component)
MATCH (app:Application)-[:DEPENDS_ON*1..3]->(comp)
WITH v, comp, app,
CASE
WHEN app.isInternetFacing AND app.handlesSensitiveData THEN 3
WHEN app.isInternetFacing THEN 2
WHEN app.handlesSensitiveData THEN 1
ELSE 0
END AS exposure_score
RETURN v.cveId AS cve_id,
v.cvssScore AS cvss_score,
sum(exposure_score) AS blast_radius,
collect(DISTINCT app.name) AS affected_apps,
CASE
WHEN v.cvssScore >= 9.0 AND sum(exposure_score) >= 2 THEN 'CRITICAL'
WHEN v.cvssScore >= 7.0 AND sum(exposure_score) >= 1 THEN 'HIGH'
WHEN v.cvssScore >= 4.0 THEN 'MEDIUM'
ELSE 'LOW'
END AS priority
ORDER BY blast_radius DESC, v.cvssScore DESC
""")
return [PrioritisedVulnerability(**record.data()) for record in results]
This pipeline combines CVSS severity with contextual blast radius to produce a single prioritised list. A CVE with CVSS 9.0 in an isolated internal tool drops to MEDIUM priority, while a CVSS 6.5 in an internet-facing customer data service rises to CRITICAL.
Attack Path Analysis: From Vulnerability to Crown Jewel
Quantifying blast radius is one thing. Finding the shortest exploitable path from an attacker-accessible vulnerability to a critical asset is another level of analysis entirely. The following Cypher query chains together reachability, network connectivity, and data sensitivity to surface the most dangerous attack paths:
// Find the shortest exploitable path from an internet-facing
// vulnerability to a crown-jewel data store
MATCH (cve:CVE)
WHERE cve.cvssScore >= 7.0
AND cve.epssScore > 0.5
AND cve.kevPublished IS NOT NULL
// Step 1: Which components are affected by this CVE?
MATCH (cve)-[:IMPACTS]->(comp:Component)
// Step 2: Which internet-facing applications depend on those components?
MATCH (app:Application)-[:DEPENDS_ON*1..5]->(comp)
WHERE app.isInternetFacing = TRUE
// Step 3: What hosts run those applications?
MATCH (app)-[:RUNS_ON]->(host:Host)
// Step 4: Can attackers traverse from the exposed host to a sensitive asset?
MATCH path = shortestPath(
(host)-[:CONNECTS_TO*1..10]->(target:Host)
)
WHERE ANY (tapp IN [(target)<-[:RUNS_ON]-(tapp:Application)
WHERE tapp.handlesSensitiveData = TRUE]
WHERE true)
RETURN cve.id AS Vulnerability,
app.name AS EntryPoint,
host.hostname AS ExposedHost,
length(path) AS NetworkHopsToTarget,
nodes(path) AS AttackPath,
cve.cvssScore AS CVSS,
cve.epssScore AS EPSS
ORDER BY cve.epssScore DESC, cve.cvssScore DESC
LIMIT 20
This query does not just list vulnerable applications — it traces the actual network path an attacker would follow. The result set tells a security team: "These 20 CVEs represent the shortest paths from the internet to your sensitive data." Each row is a concrete, prioritised remediation target with full provenance.
EPSS-Integrated Risk Scoring
CVSS tells you how severe a vulnerability is. EPSS tells you how likely it is to be exploited. A knowledge graph can combine both with blast radius context to produce a composite risk score:
// Risk-weighted vulnerability prioritisation
// Score = CVSS × EPSS × blast_radius_multiplier
MATCH (cve:CVE)
WHERE cve.epssScore > 0.1 // Filter out negligible exploit probability
MATCH (cve)-[:IMPACTS]->(comp:Component)
MATCH (app:Application)-[:DEPENDS_ON*1..5]->(comp)
WITH cve, app,
CASE
WHEN app.isInternetFacing = TRUE AND app.handlesSensitiveData = TRUE THEN 3.0
WHEN app.isInternetFacing = TRUE THEN 2.0
WHEN app.handlesSensitiveData = TRUE THEN 1.5
ELSE 1.0
END AS blastRadiusMultiplier
RETURN cve.id AS CVE,
cve.cvssScore AS CVSS,
cve.epssScore AS EPSS,
round(cve.cvssScore * cve.epssScore * blastRadiusMultiplier, 2) AS CompositeRisk,
app.name AS AffectedApp,
app.businessCriticality AS Criticality
ORDER BY CompositeRisk DESC
LIMIT 25
This approach directly addresses the inefficiency described earlier. A CVSS 9.0 vulnerability with EPSS 0.01 (1% exploit probability) in a non-internet-facing internal tool receives a composite risk score of just 0.09 — far lower than a CVSS 6.0 vulnerability with EPSS 0.9 in an internet-facing customer portal (composite risk 16.2). The graph does not just store data — it computes actionable intelligence.
Schema Evolution: Handling Temporal Data
Vulnerability data changes constantly. CVSS scores are revised. EPSS scores update daily. New exploits appear in the CISA KEV catalogue. Patches are released. Your graph schema must model this temporality:
// Track when vulnerability metadata was last updated
MATCH (cve:CVE {id: 'CVE-2026-XXXX'})
SET cve.lastSeen = datetime()
SET cve.epssScore = 0.87 // Updated EPSS score
SET cve.kevPublished = datetime('2026-07-01') // Now in KEV catalogue
// Log the change for audit
CREATE (change:CVEChangeLog {
cveId: cve.id,
changedAt: datetime(),
previousEpss: 0.12,
newEpss: 0.87,
reason: 'Proof-of-concept exploit published'
})
By maintaining a change log as graph nodes, you can query vulnerability drift over time: "Which CVEs in our environment saw the largest EPSS increase in the past week?" This turns a static vulnerability database into a living risk surface.
Comparison: Traditional CVSS vs. Knowledge Graph Prioritisation
The shift to a graph-based approach fundamentally alters how security teams operate:
| Feature | Traditional CVSS Prioritisation | Knowledge Graph + CVSS Prioritisation |
|---|---|---|
| Coverage | Limited to individual component context | Full dependency chain, blast radius, reachability |
| False Positives | High, treats all high-CVSS as equally urgent | Lower, focuses on reachable, exploitable, and high-impact |
| Remediation Speed | Slower, reactive, broad, untargeted patching | Faster, proactive, targeted patching based on actual risk |
| Team Alignment | Siloed, security vs. operations/development | Collaborative, shared context for risk and impact |
| Context | Isolated vulnerability score | Systemic risk, business impact, attack path analysis |
| Decision Basis | Severity-driven | Risk-driven (severity + context + impact) |
Real-World Application: Surviving the June 2026 Batch
Imagine facing Microsoft's June 2026 Patch Tuesday with a knowledge graph in place. Instead of a flat list of 206 CVEs, your security team receives an intelligence report:
- "Of the 206 CVEs, only 15 directly impact components present in our production environment."
- "Of those 15, 3 are in internet-facing services that handle customer data, and are reachable via known network paths. These are critical."
- "Another 5 impact internal services, but are not reachable from the internet and have compensating controls. Prioritise these next."
- "The remaining 7 are in development or staging environments and can be addressed in the next sprint."
This level of granular, context-aware prioritisation transforms an unmanageable deluge into an actionable, defensible plan. It empowers teams to focus resources on the vulnerabilities that truly matter to their unique risk posture, rather than chasing every high-CVSS score.
Building Your Dependency Graph: A Practical Implementation Guide
Constructing a vulnerability knowledge graph from scratch may sound daunting, but the incrementally valuable nature of knowledge graphs means you start small and expand. Here is a phased approach:
Phase 1: Inventory and Core Schema (Week 1)
Start by modelling your application-to-component dependencies. This alone captures the most critical dimension: what depends on what. Use your existing SBOM (Software Bill of Materials) tooling or package lock files as the data source:
# Example: Generate SBOM from npm using CycloneDX
npm install -g @cyclonedx/cyclonedx-npm
cyclonedx-npm --output-format JSON > sbom.json
Write a simple ingestion script that parses the SBOM and creates (:Application)-[:DEPENDS_ON]->(:Component) relationships in Neo4j. At this stage, you can already answer: "Which of our applications would be affected by a Log4j-style zero-day?"
Phase 2: CVE and EPSS Integration (Week 2)
Ingest the National Vulnerability Database (NVD) feed and filter for CVEs affecting your inventoried components. The NVD provides a JSON data feed updated every two hours. Augment each CVE node with EPSS scores from FIRST's daily API:
# Example: Fetch EPSS score for a CVE
import requests
def get_epss_score(cve_id: str) -> float:
resp = requests.get(
f"https://api.first.org/data/v1/epss?cve={cve_id}"
)
data = resp.json()
return float(data["data"][0]["epss"])
Create (:CVE)-[:IMPACTS]->(:Component) relationships and populate epssScore, cvssScore, and kevPublished properties. You now have a graph that can answer: "Which of our CVEs are being actively exploited right now?"
Phase 3: Network Topology and Blast Radius (Week 3)
Add your infrastructure layer: (:Host) nodes, (:Application)-[:RUNS_ON]->(:Host), and (:Host)-[:CONNECTS_TO]->(:Host) relationships. Network connectivity data can be sourced from your configuration management database (CMDB), cloud provider APIs, or network scanning tools.
This unlocks attack path analysis: "Show me every internet-accessible host running a component with a known-exploited CVE, and trace every network path to a database with sensitive data."
Phase 4: Automation and Alerting (Ongoing)
The final phase connects the graph to your incident response workflow. Configure periodic ingestion jobs that:
- Fetch the latest NVD updates and create new
CVEnodes - Reconcile EPSS scores daily (they change as exploit activity evolves)
- Cross-reference the CISA KEV catalogue and set
kevPublishedon matching CVEs - Run composite risk scoring queries and push the top 20 results to your SIEM or ticketing system
At this stage, your knowledge graph runs continuously, providing a ranked, context-aware vulnerability backlog that updates in lockstep with the threat landscape.
Further Reading
- Content dependency and supply chain attack analysis with Neo4j — How LLM agents can use Neo4j to analyse software supply chain relationships, complementing the dependency graph approach described here.
- Graph Theory for Software Engineers — The graph algorithms behind shortest-path, centrality, and community detection that power blast radius analysis.
- What Are Knowledge Graphs? — A foundational introduction to property graphs, RDF, and the architectural principles that make knowledge graphs suitable for vulnerability correlation.
- Ontology in Graph Databases — Lessons from building a production Neo4j ontology, covering node label semantics, relationship design, and constraint enforcement directly applicable to vulnerability graph schema design.
- The Context Layer Race: Why Knowledge Graphs Are Becoming the Brain of Enterprise AI — Context on how knowledge graphs serve as the reasoning layer for AI-driven security operations.
Conclusion: An Operational Necessity
As AI-assisted bug finding tools become more sophisticated and widespread, the rate of vulnerability discovery will only accelerate. The "AI patch tsunami" is not a one-off event but the new normal. Relying solely on static severity scores is no longer sustainable; it is a recipe for burnout and increased organisational risk.
Knowledge graphs for vulnerability prioritisation are shifting from a "nice-to-have" capability to an operational necessity. By providing a living, interconnected map of your digital estate, they empower security teams to identify, understand, and mitigate risk with unparalleled precision and speed. The phased approach — starting with SBOM ingestion, layering in EPSS and CISA KEV intelligence, adding network topology, then automating the pipeline — means any team can begin realising value within a week and scale incrementally from there.
The architecture is proven. The tooling is mature. The data sources are freely available. The question is no longer whether knowledge graphs improve vulnerability prioritisation — it is whether your organisation can afford to keep prioritising without one. The time to build your dependency graph is now.