Skip to content

mez-0/vulnify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

vulnify

A CVE ingestion and enrichment pipeline that builds a single normalised SQLite database stitched from CVEProject, NVD, CISA KEV, EPSS, and OSV β€” plus a Streamlit explorer for the result.

Ships as a fully populated SQLite DB on each release so you don't have to wait hours for the initial ingest.


What's in it

  • Full CVE corpus from the upstream CVEProject cvelistV5 zip
  • NVD bulk merge for CVSS metrics, CPE configurations, vuln status, and references
  • CISA Known Exploited Vulnerabilities with date_added, due_date, ransomware flag, and required action
  • EPSS exploit probability + percentile, batched
  • OSV package mapping per CVE
  • Streamlit explorer with ~70 pre-built views over the data
  • Resume-safe pipeline state β€” interruptions pick up where they left off

Current snapshot (May 2026 release):

count
CVEs 347,248
Vendors 21,183
Products 70,266
KEV entries 1,586
Tables 20

Documentation

Deeper references live in docs/:

  • Data sources β€” every upstream feed, endpoint, and licence.
  • Pipeline β€” ingestion β†’ enrichment, watermarks/resume, the cascade+heal model, tri-state discipline.
  • Schema β€” the 20-table layout and query gotchas.
  • MCP server β€” the 7 tools, per-agent setup, troubleshooting.
  • Explorer β€” the Streamlit dashboard, tab by tab.

Quick start (use the pre-built DB)

If you just want to query the data without running the pipeline:

# 1. Clone and pick the extras you need
git clone https://github.com/mez-0/vulnify.git
cd vulnify
uv sync --extra mcp          # for agents / MCP server
# or:  uv sync --extra explore   # for the Streamlit dashboard
# or:  uv sync --all-extras      # for everything

# 2. Grab the latest release DB
gh release download -R mez-0/vulnify --pattern vulnify.db.zst
zstd -d vulnify.db.zst -o vulnify.db

# 3. Point .env at the DB
cat > .env <<EOF
VULNIFY_SQLITE_PATH=$(pwd)/vulnify.db
VULNIFY_SCHEMA_SQL_PATH=vulnify/db/schema.sql
EOF

# 4a. Use it from an agent (see "MCP server" below for full setup)
uv run vulnify-mcp </dev/null     # smoke test β€” should start then exit on EOF

# 4b. Or browse it in the Streamlit explorer
uv run streamlit run explore/app.py

# 4c. Or just query the SQLite directly
sqlite3 vulnify.db "SELECT cve_id, summary FROM cve WHERE cve_id = 'CVE-2024-3094';"

Building the DB from scratch

uv sync
cp .env.example .env       # edit paths as needed
uv run vulnify-gather

The first run takes several hours without an NVD API key (the public NVD rate limit is ~5 requests / 30 seconds). With a key it's roughly 10x faster.

Get a free key at https://nvd.nist.gov/developers/request-an-api-key and set VULNIFY_NVD_API_KEY in .env.

Skipping phases

vulnify-gather exposes flags so you can re-run individual enrichment stages without re-ingesting:

vulnify-gather --skip-ingestion        # don't re-download the CVEProject zip
vulnify-gather --skip-nvd              # skip NVD bulk merge
vulnify-gather --skip-kev              # skip CISA KEV
vulnify-gather --skip-epss             # skip EPSS batch
vulnify-gather --skip-osv              # skip OSV per-CVE lookups
vulnify-gather --skip-nuclei          # skip Nuclei exploit-template ingest
vulnify-gather --skip-exploitdb       # skip Exploit-DB artefact ingest
vulnify-gather --skip-metasploit      # skip Metasploit module-metadata ingest

The pipeline writes phase watermarks to the pipeline_run table, so re-runs are idempotent β€” NVD only fetches changes since the last watermark, KEV re-syncs in full, EPSS only updates stale scores.

The exploit phases (Nuclei / Exploit-DB / Metasploit) fetch and cache their corpora under ~/.vulnify/cache and watermark on corpus version (commit SHA / content hash). They re-parse whenever cvelistV5 ingestion ran this gather β€” an ingest cascade-deletes a re-touched CVE's artefacts, so they're healed on the next run β€” and otherwise auto-skip an unmoved corpus. An explicit --skip-* always wins. The tri-state exploit.public_poc / exploit.metasploit summary bools are materialised from the artefacts: null until the contributing sources have run, then true/false.


Configuration

All config lives in .env at the project root (loaded by vulnify.settings).

Variable Required Default Purpose
VULNIFY_SQLITE_PATH yes vulnify.db Where the SQLite DB lives. Empty/unset switches the CVEProject ingest to print-only mode.
VULNIFY_SCHEMA_SQL_PATH yes vulnify/db/schema.sql DDL used to bootstrap a new DB.
VULNIFY_KEV_JSON_PATH no known_exploited_vulnerabilities.json Local copy of the CISA KEV catalog. If unset and the default file isn't present, the provider fetches it.
VULNIFY_NVD_API_KEY no β€” NVD API key. Without it you're rate-limited to ~5 req / 30s.
VULNIFY_CACHE_DIR no ~/.vulnify/cache Where the exploit corpora (Nuclei / Exploit-DB / Metasploit) are cached.

Relative paths are resolved against the project root.


Architecture

                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                  β”‚  gather.py (CLI)  β”‚
                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β”‚
                            β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚   providers/cveproject.py   β”‚  bulk download + parse
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚   db/sqlite_store.py        β”‚  upsert into normalised tables
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                             β”‚
                             β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β”‚   providers/enrichment.py              β”‚
         β”‚   β”œβ”€β”€ cisa_kev   (CISA KEV catalog)    β”‚
         β”‚   β”œβ”€β”€ nvd        (CVSS, CPE, status)   β”‚
         β”‚   β”œβ”€β”€ epss       (probability scores)  β”‚
         β”‚   β”œβ”€β”€ osv        (package mappings)    β”‚
         β”‚   └── exploit_ingest (Nuclei/EDB/MSF)  β”‚
         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              β”‚
                              β–Ό
         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
         β–Ό                                         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   explore/app.py (Streamlit)  β”‚   β”‚   vulnify/mcp.py (FastMCP)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Source layout

vulnify/
β”œβ”€β”€ vulnify/
β”‚   β”œβ”€β”€ gather.py             # `vulnify-gather` CLI (ingestion + enrichment)
β”‚   β”œβ”€β”€ mcp.py                # `vulnify-mcp` FastMCP server (stdio)
β”‚   β”œβ”€β”€ settings.py           # .env loader + path resolvers
β”‚   β”œβ”€β”€ constants.py          # Severity, SourceTrust, ExploitMaturity enums
β”‚   β”œβ”€β”€ cvss_severity.py      # CVSS-base -> severity bucketing
β”‚   β”œβ”€β”€ http.py               # shared httpx client + file/zip download helpers
β”‚   β”œβ”€β”€ models/               # dataclass models (CVE, Vendor, Product, CVSS, …)
β”‚   β”œβ”€β”€ providers/
β”‚   β”‚   β”œβ”€β”€ cveproject.py     # CVEProject bulk parser
β”‚   β”‚   β”œβ”€β”€ nvd.py            # NVD 2.0 API client + per-CVE merge
β”‚   β”‚   β”œβ”€β”€ cisa_kev.py       # KEV catalog ingest
β”‚   β”‚   β”œβ”€β”€ epss.py           # FIRST EPSS batch enrichment
β”‚   β”‚   β”œβ”€β”€ osv.py            # OSV per-CVE package lookup
β”‚   β”‚   β”œβ”€β”€ exploit_ingest.py # Nuclei/Exploit-DB/Metasploit artefact ingest
β”‚   β”‚   β”œβ”€β”€ vendor_advisories.py
β”‚   β”‚   β”œβ”€β”€ enrichment.py     # phase orchestrator
β”‚   β”‚   └── enrichment_resume.py
β”‚   └── db/
β”‚       β”œβ”€β”€ schema.sql        # 20-table normalised schema + FTS5 index
β”‚       β”œβ”€β”€ sqlite_store.py   # connection + upsert API
β”‚       β”œβ”€β”€ readonly.py       # shared read-only sqlite3 helper
β”‚       β”œβ”€β”€ cve_upsert.py     # CVE/vendor/product writes
β”‚       β”œβ”€β”€ migrate.py        # idempotent migrations + FTS5 backfill
β”‚       β”œβ”€β”€ pipeline_state.py # phase watermarks
β”‚       └── sql_conversion.py # model <-> row helpers
β”œβ”€β”€ explore/
β”‚   β”œβ”€β”€ app.py                # Streamlit dashboard
β”‚   β”œβ”€β”€ data.py               # overview queries
β”‚   └── data_views.py         # per-chart queries
└── tests/                    # pytest suite (no network, no real DB)

Schema overview

20 tables, all normalised, foreign keys enabled. Top-level CVE plus separate tables for the high-cardinality bits (CPE matches, references, CVSS metrics, exploit signals + artefacts, KEV listing, intel) so you can join cleanly.

cve                β€” primary CVE record
vendor             β€” deduped vendors (case/whitespace-normalised)
product            β€” products owned by a vendor
cve_vendor         β€” many-to-many with `role` (assigner, affected, source, …)
affected_product   β€” CVE <-> product affected/not-affected with notes
version_range      β€” per affected_product version constraints
cwe / cve_cwe      β€” weakness taxonomy
cvss               β€” every CVSS vector from any source, deduped on (source, version, vector)
cpe_match          β€” NVD configuration leaf matches
exploit            β€” exploit availability + maturity (tri-state summary bools)
exploit_artefact   β€” per-CVE exploit evidence (Nuclei/Exploit-DB/Metasploit)
kev                β€” CISA KEV listing fields per CVE
intel              β€” generic threat-intel rows (kind/value/source)
intel_string_list  β€” backing list values for intel rows
reference          β€” URL references with trust + source
reference_tag      β€” many-to-many for ref tag vocabulary
tag / cve_tag      β€” CVE-level tags
pipeline_run       β€” per-phase watermarks for resume

Full DDL is in vulnify/db/schema.sql; an annotated walkthrough with query gotchas is in docs/SCHEMA.md.


MCP server (vulnify-mcp)

vulnify.mcp exposes the SQLite database to AI agents over the Model Context Protocol. It's a FastMCP server speaking stdio, so agents like Claude Code, Claude Desktop, and Cursor spawn it as a subprocess on demand.

Full tool reference, per-agent setup, and troubleshooting: docs/MCP.md.

Step 1 β€” set up the project

git clone https://github.com/mez-0/vulnify.git
cd vulnify
uv sync --extra mcp

Step 2 β€” get a SQLite database

Two options:

Option A β€” pre-built (recommended). Download the release asset and decompress:

gh release download -R mez-0/vulnify --pattern vulnify.db.zst
zstd -d vulnify.db.zst -o vulnify.db

Option B β€” build from source. Takes hours; see Building the DB from scratch.

Step 3 β€” point the server at the DB

Create .env at the project root with an absolute path:

echo "VULNIFY_SQLITE_PATH=$(pwd)/vulnify.db" > .env
echo "VULNIFY_SCHEMA_SQL_PATH=vulnify/db/schema.sql" >> .env

Step 4 β€” smoke test

Confirm the server starts and shuts down cleanly. It speaks stdio, so just sending Ctrl-D (EOF) exits it:

uv run vulnify-mcp </dev/null

If you used a release DB built before FTS5 shipped, you'll see a one-off ~8 second backfill on stderr:

INFO   Backfilling FTS5 index over 347248 CVE rows β€” one-off, takes ~30s
INFO   FTS5 backfill complete in 8.5s

That's it β€” releases from v2026.05.21.1 onwards ship with the FTS index pre-built, so this step is silent.

Step 5 β€” wire it into your agent

Claude Code

Easiest is the CLI:

claude mcp add vulnify \
  --scope user \
  -- uv run --directory "$(pwd)" vulnify-mcp

Or edit ~/.claude.json directly:

{
  "mcpServers": {
    "vulnify": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/vulnify", "vulnify-mcp"]
    }
  }
}

Verify it's connected:

claude mcp list

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "vulnify": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/vulnify", "vulnify-mcp"]
    }
  }
}

Restart Claude Desktop after editing.

Cursor

Edit ~/.cursor/mcp.json (or the project-local .cursor/mcp.json):

{
  "mcpServers": {
    "vulnify": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/vulnify", "vulnify-mcp"]
    }
  }
}

Tools the agent can call

Tool What it does
get_cve(cve_id) Full hydrated CVE record β€” CVSS, KEV, EPSS, exploits, references, CWEs, affected products, plus an artefacts evidence list.
search_cves(...) Filter by vendor / product / cwe / kev_only / min_cvss / min_epss / year.
exploits_for(cve_id) Concrete exploit artefacts (Nuclei / Exploit-DB / Metasploit) with source, stable id, url, and exact/parsed confidence.
references_for(cve_id, tag) Reference URLs with their tags, optionally filtered to one tag (e.g. patch, exploit).
list_kev(since, limit) Recent CISA KEV entries, newest first.
database_overview() Totals, published-date range, last pipeline-run times.
search_cves_text(query, limit) FTS5 free-text search over title / summary / technical details. AND / OR / NOT / quoted phrases / prefix* all supported.

All tools return JSON-serialisable dicts. Limits cap at 100 rows.

Example agent prompts

  • "What's CVE-2024-3094?"
  • "Show me KEV entries added in the last 60 days about Cisco."
  • "Find CVEs about container escape with CVSS β‰₯ 8."
  • "Which products have the most CVEs published in 2025?"

Troubleshooting

  • Tools don't show up in an already-running agent β€” MCP servers are attached at session start, not when claude mcp add runs. Exit your current Claude Code / Cursor session and start a fresh one β€” don't /resume an old one, that re-reads the old tool list.
  • claude mcp list shows βœ— Failed to connect β€” first thing to check is the exact Args: field with claude mcp get vulnify; a copy-paste truncation that turns vulnify-mcp into vulnify-mc is a common one. Smoke test the spawned command verbatim in a shell: uv run --directory /path/to/vulnify vulnify-mcp </dev/null.
  • uv: command not found in the agent's process β€” agents inherit a minimal PATH. Either use an absolute path to uv in the config (command: "/home/you/.cargo/bin/uv") or symlink uv into /usr/local/bin.
  • VULNIFY_SQLITE_PATH is unset at startup β€” your .env isn't being picked up. The server resolves .env relative to the vulnify package root; pass an absolute --directory in the agent config so it lands in the right place.
  • Tool calls return nothing β€” confirm the DB has data with uv run python -c "import sqlite3; print(sqlite3.connect('vulnify.db').execute('SELECT COUNT(*) FROM cve').fetchone())".
  • Logs disappearing β€” vulnify-mcp logs to stderr (so it doesn't corrupt the MCP protocol on stdout). Most agent UIs surface stderr in their MCP logs view; in Claude Code that's /mcp then pick the server.

Explorer

explore/app.py is a Streamlit dashboard with roughly 70 pre-built views over the data: severity distributions, KEV listing lag, EPSS density, CWE co-occurrence heatmaps, vendor / product pressure, CVSS vector breakdowns, publication recency, exploit-in-the-wild monthly trends, and more.

uv sync --extra explore
uv run streamlit run explore/app.py

Views are organised into 14 tabs (Overview, Vendors, Trends, Vendor links, Severity & CWE, Enrichment, Exploit landscape, KEV deep dive, CVSS vector, Products & CPEs, References, Threat intel, Lifecycle, Pipeline health) and split across two query modules: explore/data.py (overview / landing) and explore/data_views.py (the per-chart queries). Full tab-by-tab breakdown and how to add a view: docs/EXPLORE.md.


Tests

uv run pytest

Tests are hermetic β€” no network, no real DB. They cover:

  • SQLite store upsert + dedup behaviour
  • Schema migration + backfill
  • NVD bulk merge
  • Pipeline state watermarks
  • Enrichment resume logic
  • Explorer query shapes
  • MCP tool surface (structured + FTS5 search)

Releases

Each release ships:

  • vulnify.db.zst β€” the full SQLite DB, zstd-compressed. Includes the FTS5 index pre-built from v2026.05.21.1 onwards so vulnify-mcp is ready to serve immediately on first start. Decompress with zstd -d vulnify.db.zst.
  • schema.sql β€” the DDL, in case you only want the structure.

Tag scheme:

  • vYYYY.MM.DD β€” major snapshot, data captured on that date.
  • vYYYY.MM.DD.N β€” code patch against the same data snapshot (e.g. v2026.05.21.1 is the 2026-05-21 data with the vulnify-mcp server added).

The code version in pyproject.toml follows SemVer independently.


License

Released under the MIT License.

About

CVE ingestion + enrichment pipeline with an MCP server for AI agents.

Topics

Resources

License

Stars

11 stars

Watchers

0 watching

Forks

Contributors

Languages