A vulnerability intelligence dashboard that pulls CVE data from multiple official sources and presents it in a single pane of glass. Built for security analysts, VM teams, or anyone who wants a self-hosted way to stay on top of vulnerabilities without tab-switching between NVD, CISA KEV, and EPSS.
- Aggregates data from NVD, CISA KEV, EPSS, and CVE.org on configurable intervals
- Normalizes everything into PostgreSQL so you can query across sources
- Serves a React frontend with a customizable drag-and-drop dashboard (clickable stats that drill into the CVE list), a unified CVE query console (simple + expert modes), detailed CVE pages, and a side-by-side CVE comparison view
- Lets analysts pin vendors and products to a personal watchlist — surfaces matching CVEs in a dedicated page and a dashboard widget; stored in browser local storage so each visitor gets their own
- Pushes live updates over WebSocket as new data syncs in
Coming soon
| Layer | Stack |
|---|---|
| Backend | Python 3.12+, FastAPI, async SQLAlchemy (asyncpg), Alembic, APScheduler |
| Frontend | React 18, TypeScript, Vite, TailwindCSS, React Query, Zustand, Recharts |
| Database | PostgreSQL 16 |
| Deployment | Docker Compose |
The fastest way to get running is with Docker Compose:
# Clone the repo
git clone https://github.com/codekeanu/cve-snoop.git
cd cve-snoop
# Set up your env (defaults work fine for local dev)
cp .env.example .env
# Build and run
docker compose up --buildOnce everything is up:
- Frontend - http://localhost:3000
- API - http://localhost:8000
- API Docs (Swagger) - http://localhost:8000/docs
On first startup, the backend kicks off an initial sync from all data sources. NVD is the big one and takes a while depending on whether you have an API key.
Without an API key, NVD rate-limits you pretty hard. Grab a free one from NVD and drop it in your .env:
NVD_API_KEY=your-key-here
If you want to work on the frontend or backend individually outside of Docker:
cd backend
pip install -e ".[dev]"
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000You'll still need a running PostgreSQL instance — easiest to just docker compose up db and point your DATABASE_URL at it.
cd frontend
npm install
npm run devVite dev server runs at http://localhost:5173 with hot reload.
# Backend (uses in-memory SQLite, no Postgres needed)
cd backend
pytest
# Frontend
cd frontend
npm run testAll endpoints live under /api/v1. Here are the main ones:
| Method | Endpoint | Description |
|---|---|---|
GET |
/cves |
Paginated CVE list with search, sort, and filters (severity, vendor, product, KEV, EPSS, date range, exploit status) |
GET |
/cves/{cve_id} |
Full CVE detail with CVSS, references, CPE, KEV, EPSS |
GET |
/cves/{cve_id}/timeline |
Timeline of events for a CVE |
GET |
/cves/{cve_id}/epss-history |
EPSS score history over time |
GET |
/dashboard/summary |
Aggregated stats for the dashboard |
POST |
/watchlist/matches |
Stateless: takes {entries: [{vendor, product?, label?}], days, limit} and returns recent CVEs matching any entry |
GET |
/sync/status |
Current sync state for all data sources |
POST |
/sync/{source}/trigger |
Manually kick off a sync (rate-limited) |
POST |
/sync/kev/backfill |
Enrich every KEV CVE still missing NVD data (bypasses the per-run cap; single-flight) |
GET |
/health |
Health check |
WS |
/ws |
WebSocket feed for live CVE updates |
Check out the full Swagger docs at /docs when the backend is running.
| Source | Default Interval | What It Provides |
|---|---|---|
| NVD | Every 2 hours | CVE records, CVSS scores, references, weaknesses, CPE matches |
| CISA KEV | Every 6 hours | Known Exploited Vulnerabilities catalog |
| EPSS | Daily | Exploit Prediction Scoring System scores |
| CVE.org | Every 4 hours | Supplementary CVE data |
All intervals are configurable via environment variables (SYNC_INTERVAL_NVD, etc.) in seconds.
The first NVD sync only pulls CVEs modified in the last 7 days, so older KEV catalog entries get created as stubs with no CVSS or references. Every KEV sync then fetches a batch of stubs from NVD's single-CVE endpoint (paced at the API rate limit) — KEV_ENRICH_PER_RUN caps each scheduled batch. To drain the entire stub queue in one shot, hit POST /sync/kev/backfill.
backend/
app/
main.py # FastAPI app setup, lifespan, CORS
config.py # Pydantic settings (env-based)
database.py # Async SQLAlchemy engine + sessions
models/ # ORM models
schemas/ # Pydantic response schemas
routers/ # API route handlers
services/ # Business logic
ingestion/ # Data source sync clients + scheduler
ws/ # WebSocket connection manager
alembic/ # Database migrations
tests/ # pytest suite
frontend/
src/
pages/ # Dashboard, CVE list, CVE detail, CVE compare, watchlist
components/ # UI components (layout, dashboard widgets, tables, feed)
api/ # Axios API client
hooks/ # React Query hooks, WebSocket hook
store/ # Zustand stores (live feed, dashboard layout, watchlist)
types/ # TypeScript interfaces
utils/ # Helpers (severity colors, date formatting)
All configuration is done through environment variables. See .env.example for the full list. The key ones:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
see .env.example | PostgreSQL connection string |
NVD_API_KEY |
(empty) | NVD API key for higher rate limits |
CORS_ORIGINS |
localhost:5173, localhost:3000 | Allowed CORS origins |
SYNC_INTERVAL_NVD |
7200 | NVD sync interval in seconds |
SYNC_INTERVAL_KEV |
21600 | CISA KEV sync interval in seconds |
SYNC_INTERVAL_EPSS |
86400 | EPSS sync interval in seconds |
SYNC_INTERVAL_CVE_ORG |
14400 | CVE.org sync interval in seconds |
KEV_ENRICH_PER_RUN |
20 | Max KEV stub CVEs to enrich from NVD each scheduled KEV sync. Each call is paced at the NVD rate limit; use POST /sync/kev/backfill to drain the full queue. |
MIT