Skip to Main Content
July 09, 2026

Vulnify: Giving Your Agents a CVE Brain

Written by Brandon McGrath
Artificial Intelligence (AI)

When building agentic components for pentesting, CVEs are inevitably going to come up. I never found anything that matched what I needed; I did not want "search the web and hope," but actual answers bound to a technology, and the ability to answer questions like “Does this version of this package have this CVE?”, “Is there a public exploit?”, “and “Where is it?” By packaging all that up, agents can get a head start on the scope of the product they’re looking at.

Prior to this work, there are a few ways it can be fudged. The first is by letting the agent search the web with Tavily. It sort of works, but it is slow and non-deterministic, and the agent ends up reading vendor marketing and blogspam to find a CVSS score that lives in a database somewhere. Or, even worse, the 1,000 free credits a month don’t scale - I don’t fancy Yet-Another-Subscription.

The second is by bolting on a scanner like Nuclei. Nuclei is great at templated detection against a live host. But we may not always want to point heavy tools at a product right away. Following the approach of recon and light poking first, Nuclei doesn’t fit.

The third is by hitting NVD and vendor APIs live. This is the closest-to-right solution, but painful to do deterministically and a waste of tokens to let agents figure it out. NVD also throttles you to about five (5) requests every 30 seconds without a key. Furthermore, every source has its own schema, and nothing joins together well.

None of these solve the problem, they work around it and are probably fine for smaller projects, but they do not satisfy my stability and reproducibility requirements, let alone save tokens.

This is where Vulnify comes in. It is an MCP-capable server set on top of one (1) normalized SQLite database that stitches the authoritative sources together and hands the agent a clean, joined answer.

One (1) caveat up front, Vulnify is point-in-time. It needs to pull data every so often to stay relevant; I am working on something CICD-able for this in the future.

How It Got Built

Before I discuss the internals, I want to make a foreword on writing bigger projects with agentic workflows. I find that LLMs are great at extending a codebase that already has a strong opinion baked in. If you let an agent loose on a new project with no guardrails or guidelines, you will see all sorts of weird and wonderful coding styles.

My personal approach, and the approach used to build Vulnify, is to manually build v0.1. I know, writing code in 2026—wow. But, by doing so, I get to lay the groundwork for coding paradigms: how docstrings are laid out, how functions are named, data models, and generally writing the code how I like to. Then, I pass an agent (Claude, in my case) over top of it to learn it, write docs, and update CLAUDE.md and memory; whatever it needs to ensure it matches my style. Then, over time, I introduce subagents to reinforce that pre-commit. If this first pre-pass is done in a strong model like Opus 4.8, then all subsequent work can be done with something like Sonnet, or even local models in some cases.

From there, it is spec-driven, one (1) feature at a time: plan, grill, and build. Plan is a written spec with scope and acceptance criteria. Grill is where I stress-test that plan against the real codebase before a line is written and the load-bearing constraints and awkward edge cases turn up on paper, where they are cheap to fix. Build is implementing in slices to contain scope and context.

Doing all this keeps the codebase coherent because a human set the taste, and the tooling enforces it. It does not get re-argued by every agent that shows up.

What's Actually In It?

Vulnify is one (1) normalized SQLite database with 20 tables, foreign keys on, and everything joined on the CVE ID to create a relational graph.

The mental model is a spine plus enrichment. The spine is CVEProject/cvelistV5 because it is the authoritative CNA-submitted record set. It defines which CVEs exist and carries the core fields:

  • Summary
  • Technical details
  • Affected products
  • CWEs
  • References
  • CVSS

Everything else is enrichment layered onto CVEs that already exist. The following table lists the enrichment sources and why each one earns its place.

Source

Description

NVD 2.0

This gives the analysis layer NVD-scored CVSS, CPE applicability, and vuln status. CNAs do not always score a CVE, and NVD gives you a consistent CVSS and machine-readable CPE. It is also the slow part of a build, thanks to that rate limit.

CISA KEV

CISA KEV is the ground truth. CVSS and EPSS are predictions; KEV is confirmed exploitation in the wild, with regulatory due dates and a ransomware flag. It is the highest signal input you have for prioritization.

EPSS

This is the probability that a CVE gets exploited in the next 30 days. CVSS tells you how bad it would be if it happened. EPSS tells you how likely it is to happen. These are two (2) different questions, both worth asking.

OSV

OSV maps a CVE to affected packages across ecosystems like PyPI, npm, and Go. It is the bridge from a CVE to a concrete package and the way in for dependency and SBOM questions.

This is the exploit corpora, which is where Vulnify gets useful for offense:

Source

Description

Nuclei Templates

A CVE-tagged template means there is a real, runnable detection. The CVE ID comes from the template's structured classification field, so Vulnify records the link as exact.

Exploit-DB

This uses the files_exploits.csv from Exploit-DB. From here, the CVE IDs are scraped out of a free-text column, so those links are tagged parsed, which is a lower-trust grade.

Metasploit

This is Metasploit module metadata. If it has an MSF module, then it means this is a weaponized, operator-ready exploit, which is about the highest operational signal there is. The CVE IDs come from the module's References, so those are exact as well.

Those three (3) feed a table of concrete exploit artefacts (source, stable ID, URL, and confidence) and drive the summary signals on each CVE. A few of the data-model decisions are worth pulling out, because they are the "taste" I mentioned earlier.

The most important thing is that the exploit signals are tri-state; public_poc and Metasploit are not booleans. They are NULL, false, or true: NULL if no source has looked yet, false if a source looked and found nothing, or true if it found something. This matters because a confident "no public exploit exists" in a report, when there is one, is a liability. "Not assessed" and "assessed, found nothing" are different answers, and Vulnify never quietly turns one into the other. When the data does degrade, say, mid-rebuild, it degrades toward NULL, which is the honest direction.

“Exact” means the CVE ID came from a structured field, while “parsed” means it was extracted from free text. It tells an operator how far to trust the link. Vulnify only emits those two (2), because a link guessed from a title is worse than no link at all, so those get dropped.

Version ranges are stored as first-class structured data rather than free text, because "does version X of this thing hit this CVE?" is a real question, and string comparison is a trap. "1.10" sorts before "1.9" lexically, which is the wrong answer. Keeping the ranges structured is what will let that comparison be done properly in Python, three (3) state, instead of as lexicographic SQL. (The comparator and the version-matching tools that sit on top of it are the next thing on the roadmap; they have not shipped yet.)

There is also an FTS5 full-text index over the titles, summaries, and technical detail, so an agent can search for "container escape" as easily as it can filter by vendor.

Every source is its own idempotent, watermarked phase, so the whole pipeline is built to be re-run and pick up where it left off, which brings us back to the caveat: it is a snapshot, designed to be rebuilt.

The Sources, Raw

If you want a feel for why the stitching is worth doing once, here is each source on its own. There are eight (8) feeds, and three (3) of them are not even queryable APIs.

The spine, cvelistV5, is a bulk zip. There is no per-CVE endpoint, so you take the lot and parse a quarter of a million JSON files:

1. CURLing CVEProject

NVD 2.0 has a proper API and the rate-limit tax to go with it:

2. CURLing NVD

Without a key, you are throttled to roughly five (5) requests every 30 seconds, which is why a cold build takes hours.

EPSS is clean and keyless and comes back as { epss: "0.99999", percentile: "1.0" }:

3. CURLing OSV

OSV maps a CVE to affected packages, and only open source packages get a mapping, which becomes relevant shortly:

4. CURLing CISA KEV

CISA KEV is one (1) big JSON catalog. At the time of writing, that is catalog version 2026.07.01 with 1,631 entries:

5. CURLing CISA

Nuclei is a git repo, so you take the tarball and use the commit SHA as a version stamp:

6. CURLing Nuclei

Exploit-DB is a CSV:

7. CURLing ExploitDB

And Metasploit is one (1) large module-metadata JSON, keyed by module path:

8. CURLing Metasploit

There are eight (8) feeds and eight (8) different shapes where three (3) need a parser rather than an HTTP call. There is also a rate limit on the big one, and nothing shared between them but the CVE ID. Vulnify does that stitching once, offline, so the agent asks a single question instead of orchestrating eight (8).

One CVE

The cleanest way to show what "enriched" means is through one (1) fully populated record. CVE-2025-53770 is a good pick. It is the July 2025 SharePoint RCE that got named "ToolShell" and mass-exploited, and it has a value in every component, with exactly one (1) exploit artefact from each of the three (3) corpora.

The cvelistV5 record forms the backbone of the entry, providing the authoritative CNA-supplied metadata.

Field

Value

Title

Microsoft SharePoint Remote Code Execution Vulnerability

CNA

Microsoft

Summary

Deserialization of untrusted data, including Microsoft's note that exploitation has been observed in the wild

CWE

CWE-502

Affected Products

SharePoint 2016, SharePoint 2019, and SharePoint Subscription Edition, including version ranges

References

13 references spanning the Microsoft advisory, CISA alerts, eye.security research, and industry reporting

NVD enriches the record with standardized scoring and platform metadata.

Field

Value

CVSS Base Score

9.8

CNA Vector

Includes temporal metrics (E:F/RL:W/RC:C)

NVD Vector

Complete base scoring vector

CPE Matches

Three (3) CPE match strings

Analysis Status

Analysed

CISA's Known Exploited Vulnerabilities catalogue provides operational prioritization.

Field

Value

Listed

Yes

Date Added

July 20, 2025

Due Date

July 21, 2025

Observation

One (1) day remediation deadline, indicating exceptional urgency

EPSS contributes an estimate of exploitation likelihood.

Field

Value

EPSS Score

0.885

Percentile

99.5th percentile

Interpretation

Among the highest predicted exploitation probabilities

And from the exploit corpora, all three (3) signals are true (public PoC, Metasploit, and in the wild) and backed by the artefact triad:

Source

Artefact

Confidence

Metasploit

exploit/windows/http/sharepoint_toolpane_rce

Exact

Nuclei

CVE-2025-53770 template

Exact

Exploit-DB

EDB-52405

Parsed

The one (1) thing that is empty is the OSV package mapping, and that is correct. SharePoint is obviously proprietary, so OSV has nothing to say about it. That is the tri-state mindset applied to coverage: "assessed, nothing applies" is not the same as "did not look." For an open source CVE, the mapping is populated. CVE-2004-2462, for example, comes back with Debian:10:cplay.

One (1) simple query yields a plethora of data for your agents.

The Explorer

Before the agent side, it is worth showing that the database is just explorable. There is a Streamlit dashboard bundled in the repo, around 70 views across 14 tabs, reading the exact same database the agents query.

9. Overview: Headline Totals and How Much of the Corpus Each Enrichment Source Covers
10. Exploit Landscape: The Tri-State Summary Counts, and Artefacts by Source With Their Provenance Confidence
11. Enrichment: CISA KEV Listings by Month, With the EPSS Score Distribution and the EPSS-Against-CVSS Scatter Below It
12. Vendor Links: The Strongest Vendor Co-Occurrence Pairs, With the Heatmap Below

Talking to It: The MCP Server

The MCP server is a FastMCP server over stdio.

There are seven (7) tools, and the easiest way to think about them is by the questions they answer:

Function

Description

get_cve(cve_id)

This is the "tell me everything about this CVE" function. The fully hydrated record is: CVSS, KEV, EPSS, exploit signals, references, CWEs, affected products, CPEs, and the artefact evidence list.

search_cves(...)

This is the "find CVEs matching these criteria" by vendor, product, CWE, KEV status, minimum CVSS, minimum EPSS, or year function.

search_cves_text(query)

This is the "search the text" function, using FTS5 with the usual AND, OR, NOT phrases and prefixes.

exploits_for(cve_id)

This is the "is there a real exploit, and where?" function, returning the artefacts with source, confidence, and URL.

references_for(cve_id, tag)

This translates to "give me the patch or advisory links."

list_kev(since, limit)

This translates to "what has CISA added recently?"

database_overview()

This essentially summarizes the stats of the db.

Actually Using It

The rest is easier to show than to describe. Everything below came off the live server.

Starting with orientation, database_overview() reports 347,248 CVEs, 21,142 vendors, 70,266 products, 1,593 KEV-listed CVEs, and EPSS on all of them. The CVE data runs to April 30, 2026, and the enrichment phases last ran on June 10, 2026. The tool reports its own staleness, which is the point.

Ask it about Log4Shell with get_cve("CVE-2021-44228"), and you get the whole triage picture in one (1) call:

Signal

Value

KEV

Listed, added December 10, 2021, due December 24, 2021

EPSS

0.944 (99.96th percentile)

Exploit

Public PoC, Metasploit, and in-the-wild, all true

Artefacts

52

Pull the evidence with exploits_for("CVE-2021-44228"), and those 52 artefacts break down by source, each carrying its confidence:

Source

Count

Confidence

Nuclei

44

Exact

Metasploit

5

Exact

Exploit-DB

3

Parsed

The Metasploit module paths are the "there is a weaponized path" signal, and the exact and parsed tags tell the agent how far to trust each link.

For triage across the whole corpus, search_cves(kev_only=True, min_epss=0.9) gives you everything CISA has confirmed exploited that EPSS also puts above 90 percent:

CVE

CVSS

EPSS

What It Is

CVE-2025-47812

10.0

0.929

Wing FTP Server RCE (root/SYSTEM)

CVE-2025-64446

9.8

0.931

FortiWeb path traversal to admin command exec

CVE-2026-24061

9.8

0.911

GNU Inetutils telnetd auth bypass

CVE-2026-41940

9.8

0.908

cPanel and WHM auth bypass

And the full-text index handles the questions you cannot express as filters. search_cves_text("container escape") comes back with youki, Versa Concerto, iSula, and LXC escapes, ranked by relevance. It also surfaces one (1) loose match on "escape sequences," which is stemmed full-text search being honest about what it is.

Where it gets interesting is chaining tools together. Take a question an operator would actually ask: What is the most exploit-likely Fortinet bug CISA has flagged, and can I pop it with Metasploit? Two (2) calls.

First, search_cves(vendor="fortinet", kev_only=True) returns 25 KEV-listed Fortinet CVEs. Ranked by EPSS, the top of the list is CVE-2018-13379, the FortiOS SSL-VPN path traversal and credential-leak bug, at 0.945.

Then exploits_for("CVE-2018-13379") answers the second half:

Source

Artefact

Confidence

Metasploit

auxiliary/gather/fortios_vpnssl_traversal_creds_leak

Exact

Nuclei

CVE-2018-13379

Exact

Exploit-DB

EDB-47287, EDB-47288

Parsed

Yes. There is a Metasploit module, plus a Nuclei template and two (2) Exploit-DB entries. Two (2) calls, and the agent has a prioritized target and a runnable module path, without a single web search or a guessed CVE.

You Don't Have to Use MCP

One (1) last thing, because it is easy to assume MCP is the whole story; it is not. MCP gets it up and running super easy. But it’s not the only way to use it—after all, it’s just a database.

In a larger system I run, I skip the MCP transport entirely and import the query layer directly, the same store and query functions that the MCP tools wrap, and call them as plain Python. There are a few reasons to do that.

The first is visibility. The more I work with agentic systems, the more important this has become to me. Owning the call site means I can wrap every lookup in my own logging and see what the agent asked, what came back, how long it took, and which CVEs it touched. The stdio transport is a black box by comparison. If you want an audit trail of what your agents are doing with the data, you write the wrapper yourself. However, this can probably be resolved directly in Vulnify by allowing it to be a library.

The second is that it is just an in-process function call, with none of the subprocess or stdio overhead. The third is control: caching, my own ranking, policy, and wiring the results into the rest of the pipeline.

So the way to think about Vulnify is as a database and a query library first, and as an MCP server second. MCP is the easy way in. Direct embedding is what you reach for when you want control and visibility over how the data is used. It is one (1) example of usage, not the whole of it.

Wrapping Up

The problem was that agents doing security work have no real CVE knowledge to draw on, only workarounds. Vulnify is my attempt to give agents access to proper and enriched data regarding CVEs and to cut out a lot of the token wastage while doing so. I spent a lot of time talking about how Vulnify can be used for agents, but it’s also great deterministically. Say you have C2 automation and can extract OS information; well, now you can use that to query the installed applications or OS version too. It’s only Python, after all.