Code dependency CVE relevance with LLM_SCORE
Objective
Vulnerability scanners flag every transitive dependency with a published CVE. 80% of those flags
are false alarms because the vulnerable code path is never reached in your application. Walk the
dependency graph in Cypher, then ask LLM_SCORE "given this CVE description and how the
dependency is used, is this exploitable here?" The wow moment: a single query returns the
short list of CVEs that actually matter for your service, with the LLM's reasoning attached.
Step 1: Set up the dependency graph
MERGE (svc:Service {name: "Northwind API",
description: "Public REST API for Northwind Logistics, accepts JSON, parses XML invoices, decodes JWT auth"})
// Direct dependencies
MERGE (a1:Package {name: "axum", version: "0.7.5", lang: "rust"})
MERGE (a2:Package {name: "serde_json", version: "1.0.115", lang: "rust"})
MERGE (a3:Package {name: "quick-xml", version: "0.31.0", lang: "rust"})
MERGE (a4:Package {name: "jsonwebtoken", version: "9.3.0", lang: "rust"})
MERGE (a5:Package {name: "image", version: "0.24.7", lang: "rust"})
MERGE (a6:Package {name: "reqwest", version: "0.11.27", lang: "rust"})
// Transitive dependencies (one extra hop)
MERGE (t1:Package {name: "openssl", version: "0.10.62", lang: "rust"})
MERGE (t2:Package {name: "memchr", version: "2.7.1", lang: "rust"})
MERGE (t3:Package {name: "ring", version: "0.17.7", lang: "rust"})
// Direct edges
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a1)
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a2)
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a3)
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a4)
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a5)
MERGE (svc)-[:DEPENDS_ON {scope: "direct"}]->(a6)
// Transitive edges
MERGE (a6)-[:DEPENDS_ON {scope: "transitive"}]->(t1)
MERGE (a3)-[:DEPENDS_ON {scope: "transitive"}]->(t2)
MERGE (a4)-[:DEPENDS_ON {scope: "transitive"}]->(t3)
// Published CVEs against these packages
MERGE (cve1:CVE {id: "CVE-2026-1001", severity: "high",
description: "image: remote denial of service via crafted GIF frame counts",
affected: "image"})
MERGE (cve2:CVE {id: "CVE-2026-1042", severity: "critical",
description: "openssl: timing oracle in CBC decryption when used with PKCS7 padding",
affected: "openssl"})
MERGE (cve3:CVE {id: "CVE-2026-1099", severity: "medium",
description: "quick-xml: stack overflow on deeply nested XML if recursion guard disabled",
affected: "quick-xml"})
MERGE (cve4:CVE {id: "CVE-2026-1188", severity: "low",
description: "memchr: SIMD path miscompile on AVX-512 disabled by default",
affected: "memchr"})
MERGE (cve5:CVE {id: "CVE-2026-1210", severity: "high",
description: "jsonwebtoken: alg confusion when verifying tokens with HS256 if RS256 keys are accepted",
affected: "jsonwebtoken"})
MERGE (cve1)-[:AFFECTS]->(a5)
MERGE (cve2)-[:AFFECTS]->(t1)
MERGE (cve3)-[:AFFECTS]->(a3)
MERGE (cve4)-[:AFFECTS]->(t2)
MERGE (cve5)-[:AFFECTS]->(a4);
Step 2: Find every reachable CVE for the service
// Walk 1..2 hops to find every CVE on a direct or transitive dep.
MATCH (svc:Service {name: "Northwind API"})-[:DEPENDS_ON*1..2]->(pkg:Package)<-[:AFFECTS]-(cve:CVE)
RETURN DISTINCT cve.id AS cve,
cve.severity AS severity,
pkg.name AS package,
cve.description AS description
ORDER BY severity DESC;
Step 3: Have LLM_SCORE judge real exploitability
// Now grade each CVE for whether it actually applies given how the service uses the dep.
MATCH (svc:Service {name: "Northwind API"})-[:DEPENDS_ON*1..2]->(pkg:Package)<-[:AFFECTS]-(cve:CVE)
WITH svc, pkg, cve,
llm_score(
"Given this service description: '" + svc.description +
"' and this CVE: '" + cve.description +
"', score 0..1 the chance the vulnerability is reachable in this service. " +
"Score high only if the vulnerable code path is plausibly hit by user input.",
cve
) AS exploitability
RETURN cve.id AS cve, cve.severity AS published_severity, pkg.name AS package,
exploitability,
CASE WHEN exploitability > 0.7 THEN "FIX NOW"
WHEN exploitability > 0.4 THEN "investigate"
ELSE "noise"
END AS triage
ORDER BY exploitability DESC;
What's happening
[:DEPENDS_ON*1..2]walks both direct and transitive deps with one variable-length hop.*1..Nextends to deeper transitive trees.- The published CVE severity is what most scanners report;
LLM_SCOREproduces an additional contextual severity that reads the service description and the CVE text to ask "is this reachable here?" - For the demo: the GIF DoS in
image(which the API actually calls) and the JWT alg confusion injsonwebtoken(auth code path) score high; the AVX-512 miscompile inmemchrand the XML stack overflow with the default recursion guard enabled score low. - The triage CASE turns the score into a workflow signal — straight into ticket priority.
- Pair with recipe 014 (cyber threat graph): export today's exploitable CVEs as
:Vulnerabilitynodes and run attack-path analysis from your public-facing services through them.
Try this next
MATCH (svc:Service)-[:DEPENDS_ON]->(direct:Package)
RETURN svc.name, count(direct) AS direct_dep_count;
MATCH (cve:CVE)-[:AFFECTS]->(p:Package)<-[:DEPENDS_ON*1..3]-(svc:Service)
WHERE cve.severity IN ["high", "critical"]
RETURN cve.id, cve.severity, p.name, count(DISTINCT svc) AS impacted_services
ORDER BY impacted_services DESC;
// Packages with no published CVEs — the boring-good kind.
MATCH (p:Package)
OPTIONAL MATCH (cve:CVE)-[:AFFECTS]->(p)
WITH p, count(cve) AS open_cves
WHERE open_cves = 0
RETURN p.name AS package, p.version;