A self-updating cyber threat intelligence dashboard.
Aggregates security news, CISA advisories, CVEs, exploited-vulnerability data, and ransomware activity into one place — with an IOC lookup tool and built-in analytics.
Live Demo · Features · Quick Start · Tech Stack · API Reference
Security teams (and anyone tracking the threat landscape) have to check a dozen different places — news sites, CISA's site, NVD, ransomware leak-site trackers — just to stay current. ThreatPulse pulls all of it into a single dashboard, refreshes itself on a schedule, and turns the raw feeds into charts: severity breakdowns, top ransomware groups, targeted sectors, and volume trends over time.
- 📰 Aggregated news — pulls from The Hacker News, BleepingComputer, Krebs on Security, Dark Reading, SecurityWeek, and The Record
- 🏛️ Official advisories — live CISA advisories feed
- 🎯 Known Exploited Vulnerabilities — the CISA KEV catalog, flagged by ransomware association
- 🧬 CVE tracking — recent vulnerabilities from the NVD API with CVSS score and severity
- 🔓 Ransomware tracker — recent leak-site victim postings, searchable and paginated rather than dumped as one long list
- 🗺️ Global threat map — ransomware activity plotted by country, with drill-down into each country's most active group, top targeted sector, and recent incidents
- 📊 Built-in analytics — news volume, severity distribution, top ransomware groups, most-targeted sectors, KEV timeline — charted, not just listed
- ⏱️ Self-updating — background scheduler refreshes every source hourly (configurable), plus a manual "Refresh Now" button with live progress feedback and explicit "Last ingested / Next refresh" timestamps so it's always clear how current the data is
- 📡 Live ticker — the newest item across all five sources, typed out in real time on the landing page, so the dashboard reads as alive rather than static
- 🔍 IOC Lookup — paste a suspicious IP, domain, URL, or file hash and get a consolidated verdict: concurrent reputation checks against AbuseIPDB, VirusTotal, AlienVault OTX, and URLhaus (each behind a free, optional API key), correlation against ThreatPulse's own ingested news/advisories/ ransomware data, an explainable point-based risk score with evidence-based escalation (and built-in recognition of known test files like EICAR, so it doesn't cry wolf on a security test), and rule-based, priority-tagged next investigation steps — validated client-side before it ever hits the backend, with results cached so repeat lookups are instant
- ✅ Tested — 57 automated tests (ingestion parsing, scoring/rule engine, and API layer), CI runs on every push
- 🐳 Containerized — one
docker compose upgets the full stack running locally, database included
| Layer | Technology |
|---|---|
| Backend | FastAPI, SQLAlchemy, APScheduler |
| Frontend | React 19, React Router, Vite, Recharts, d3-geo + topojson (threat map) |
| Database | PostgreSQL (production) / SQLite (local dev) |
| Testing | Pytest, FastAPI TestClient |
| CI/CD | GitHub Actions |
| Deployment | Docker, Render, Vercel, Neon |
backend/ FastAPI + SQLAlchemy + APScheduler -> REST API, background ingestion, analytics
frontend/ React + Vite + Recharts -> dashboard UI
- Ingestion split by design: every data source has a
fetch_*()function (does the network call) and aprocess_*()function (pure parsing/upsert logic, no network) — this is what makes ingestion unit testable offline, without hitting live APIs in CI. - Dedup by design: all ingestion is upsert-based (matched on URL/CVE ID/etc.), so re-running it never creates duplicates.
- Database-agnostic: SQLite locally, Postgres in production — switched
with a single
DATABASE_URLenvironment variable, no code changes. - Resilient client-side aggregation: the live ticker combines five
independent API calls (news, advisories, CVEs, KEV, ransomware). Those are
settled individually (
Promise.allSettled, notPromise.all) so one slow or failing source — common on a free-tier host waking from cold — can't silently freeze the other four from updating.
docker compose up --buildStarts Postgres, the backend (localhost:8000), and the frontend
(localhost:5173) together.
Backend:
cd backend
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000Frontend:
cd frontend
npm install
cp .env.example .env
npm run dev| Variable | Purpose | Default |
|---|---|---|
DATABASE_URL |
Postgres connection string (falls back to local SQLite if unset) | (sqlite) |
UPDATE_INTERVAL_HOURS |
How often the scheduler re-fetches all sources | 1 |
NVD_API_KEY |
Free key from nvd.nist.gov, raises the CVE API rate limit | (none) |
CVE_LOOKBACK_DAYS |
How many days back to pull modified CVEs | 3 |
ABUSEIPDB_API_KEY |
Free key from abuseipdb.com — powers IOC Lookup's IP reputation check | (none — provider skipped gracefully) |
VIRUSTOTAL_API_KEY |
Free key from virustotal.com — powers IOC Lookup's multi-engine scan results | (none — provider skipped gracefully) |
OTX_API_KEY |
Free key from otx.alienvault.com — powers IOC Lookup's threat-pulse correlation | (none — provider skipped gracefully) |
URLHAUS_AUTH_KEY |
Free "Auth-Key" from auth.abuse.ch — powers IOC Lookup's known-malware-URL check | (none — provider skipped gracefully) |
IOC_CACHE_TTL_MINUTES |
How long an IOC Lookup result stays cached before re-querying | 60 |
IOC Lookup works out of the box with no keys at all — correlation against
ThreatPulse's own database still runs, and any provider missing a key is
reported as "not configured" rather than failing the whole lookup. See
backend/.env.example.
cd backend
pip install -r requirements-dev.txt
pytest57 tests cover ingestion parsers (via fixture RSS/JSON, no network needed),
the IOC scoring/rule engine, and the API layer (isolated test database).
.github/workflows/ci.yml runs the full suite plus a frontend production
build on every push/PR.
ThreatPulse is deployed as a real three-tier production setup rather than bundled onto a single platform — each piece was chosen for a specific reason, not just because it was free:
Browser
│
▼
Vercel (global CDN edge) → React dashboard
│ HTTPS fetch
▼
Render (Docker container) → FastAPI backend + APScheduler
│ DATABASE_URL
▼
Neon (serverless Postgres) → persisted data
| Service | Role | Why this one |
|---|---|---|
| Neon | Managed PostgreSQL | Render's free-tier filesystem is wiped on every restart/redeploy, so a local SQLite file would lose all collected data. Neon decouples the database from the app server entirely, has a genuinely permanent free tier (not a trial), and scales to zero when idle — so it costs nothing while the project sits quiet. |
| Render | Backend hosting (FastAPI, Dockerized) | Builds straight from the repo's own Dockerfile with no extra config, and — unlike most serverless platforms — supports a long-running background process, which the APScheduler ingestion job needs. |
| Vercel | Frontend hosting (React/Vite) | Serves the static build from a global CDN edge, so the dashboard itself loads fast for anyone, anywhere, independent of where the backend is running. Zero-config deploys for Vite projects, auto-redeploys on every push. |
| GitHub Actions | CI/CD + scheduled refresh | Runs the full test suite on every push before anything reaches production, and a separate scheduled workflow (scheduled-refresh.yml) pings the live backend hourly — keeping data current and reducing how often Render's free tier goes fully cold. |
Wiring between them is two environment variables: DATABASE_URL on Render
(set to the Neon connection string) and VITE_API_BASE on Vercel (set to the
Render backend URL).
| Endpoint | Description |
|---|---|
GET /api/news |
News items (?search=, ?source=, ?limit=) |
GET /api/advisories |
CISA advisories (?search=, ?limit=) |
GET /api/cves |
Recent CVEs (?search=, ?min_score=, ?limit=) |
GET /api/kev |
KEV catalog (?search=, ?ransomware_only=, ?limit=) |
GET /api/ransomware |
Ransomware victims (?group=, ?country=, ?search=, ?limit=) |
GET /api/ransomware/count |
Total matching ransomware victims, ignoring limit — powers "X of Y" pagination |
GET /api/stats |
Summary counts for the dashboard header |
GET /api/analytics/news-volume |
Daily news counts (?days=) |
GET /api/analytics/severity-distribution |
CVE counts by severity |
GET /api/analytics/top-ransomware-groups |
Top groups by victim count (?limit=, ?days=) |
GET /api/analytics/top-sectors |
Top targeted sectors (?limit=, ?days=) |
GET /api/analytics/kev-timeline |
Daily KEV catalog additions (?days=) |
GET /api/analytics/by-country |
Ransomware incident counts by country, with top group/sector per country — powers the threat map |
POST /api/refresh |
Trigger an immediate ingestion run |
POST /api/ioc/lookup |
Look up an IOC ({"indicator": "..."}) — returns risk score, provider results, correlation, and analyst guidance |
GET /api/ioc/recent |
Recently looked-up indicators (?limit=) |
- User accounts + saved watchlists (track specific vendors/CVEs/groups)
- Live alerts via Slack/Discord/email on critical CVEs or new KEV entries
- Historical trend comparisons (week-over-week, month-over-month)
ransomware.live's exact JSON field names have shifted across API versions before.backend/app/ingest/ransomware.pyreads several plausible key names defensively — if a live run logs 0 new ransomware records, print one raw record and adjust the_first(...)key lookups.- CISA has changed feed URLs before; check cisa.gov for the current advisories RSS link if that feed ever returns nothing.
Ezaz Ahmad GitHub: @Ezaz-Ahmad
MIT — see LICENSE.





