Skip to content
CVE Tracker

How it's built

CVE Tracker is one TypeScript repository: a backend that keeps its own copy of every public CVE, a server-rendered frontend, and a small package both share. It runs on a single server for about 7 € a month. This page is the tour; the repository has the rest.

The shape of it

 CVE List (GitHub)  ─┐
 NVD API 2.0        ─┤  in-process sync  ┌───────────────┐  /v1/*   ┌──────────────────────┐
 CISA KEV (JSON)    ─┼─────────────────▶ │ backend       │ ───────▶ │ frontend             │
 FIRST EPSS (CSV)   ─┤                   │ Hono + SQLite │          │ TanStack Start (SSR) │
 GitHub advisories  ─┘                   │               │          │                      │
                                         └───────────────┘          └──────────────────────┘
  • The backend reads five public sources on their own schedules into one SQLite file, and answers the public API. Nothing is fetched from a source while a visitor waits: every answer comes from the local copy.
  • The frontend renders every page on the server from that API, so a page arrives complete, is indexable and readable before any JavaScript runs; the browser then takes over and asks the API directly.
  • packages/core is the contract between them: the response shapes as zod schemas, the filter and its parsing, the typed API client and the severity model. The API's answers are checked against the same schemas the client validates with, and the OpenAPI description is generated from them.
apps/backend     HTTP API, SQLite store with query worker threads, the source sync
apps/frontend    Web app: pages, feeds, share images, badges, documents for crawlers and agents
apps/e2e         Playwright tests of the built backend and frontend together
packages/core    Shared contract, API client, severity and time-bucket model
docs             The API reference, the changelog and this page, shown on the site as they are on GitHub

Tech stack

LayerTechnologyWhy
LanguageTypeScript 7, Node 24One language end to end; the contract is one set of types
Monorepopnpm 12 workspaces, TurborepoExact versions, every dependency at least 7 days old before it installs; cached builds and tests
APIHono, zodSmall and fast, standard Request/Response; inputs and outputs validated
Storagenode:sqlite with FTS5A full database and full-text search built into Node: no native module, no database server to run
LoggingpinoStructured JSON logs with a request id on every line
Web frameworkReact 19, TanStack Start, Router, Query, Table, ChartsServer rendering, the whole filter in the URL, typed routes, cancellable data loading
DocumentsTanStack Markdown and HighlightThe API reference, changelog and this page rendered from docs/ at build time
ServerNitroThe frontend's Node server, with precompressed assets
UITailwind CSS 4, Base UI, LucideDesign tokens for light and dark themes; accessible primitives for selects and suggestions
Imagessatori, resvgShare images of each CVE drawn on the server, no browser needed
Qualityoxlint, oxfmt, Vitest, Playwright, axe-coreLint and format in milliseconds; unit, end-to-end and accessibility tests on every change
HostingDocker Compose, Cloudflare Tunnel, Hetzner Cloud CX23One small server that opens no port to the internet; Cloudflare adds TLS and a cache in front

Keeping the data current

Each source has its own loop, so a slow or failing one never holds up the others:

SourceWhat it addsHow it is readEvery
CVE List (cvelistV5)Every record, CNA scores, CISA's VulnrichmentThe whole repository once as an archive, then only what its delta log lists10 min
NVD API 2.0CVSS and CWE where the record has noneEvery CVE once, then only what NVD changed since the last read1 hour
CISA KEVKnown exploitation, ransomware use, deadlinesOne JSON file; CISA's GitHub copy when cisa.gov refuses6 hours
FIRST EPSSProbability of exploitation in the next 30 daysOne compressed CSV of every score, swapped in whole6 hours
GitHub Advisory DatabaseAffected packages and their ecosystemsThe reviewed advisories from the repository's archive12 hours
  • Nothing is lost on a failure. The CVE List sync advances only over what the delta log really contains, retries records it could not read, and a first full read resumes where it stopped.
  • Readers never see half a load. EPSS is written into a fresh table and swapped in one short transaction, so a page shows yesterday's scores or today's, never a mix.
  • The schema migrates itself. Each step runs once at start-up; a new filter that needs data the old database lacks fills it in from the stored records in the background.

Answering fast

  • Queries never block the server. Reads run in worker threads, each with its own read-only connection; a query still running after 3 seconds is stopped, and past 200 waiting calls the API answers 503 at once rather than piling up.
  • Counts are kept, not computed. CVEs per day, severity and KEV membership are kept current by triggers, so the timeline of all time adds up days instead of grouping 400,000 rows.
  • Answers are reused. The API keeps each answer for 30 seconds and drops them all when the data changes; every answer carries Cache-Control, so Cloudflare serves most traffic without reaching the server.
  • Work is cancelled. A filter changed while its results load aborts the old request, so a late answer never overwrites a newer one.

Security

  • A strict Content-Security-Policy on every page, built per response from the hashes of its inline scripts: nothing runs that was not put there by the build.
  • HSTS, nosniff, frame-ancestors 'none', a strict referrer policy and links out that pass no referrer.
  • Fair use. Every caller gets 120 requests a minute; the frontend's own rendering is counted per visitor, through a shared secret, so one busy visitor cannot use up the API for everyone.
  • Supply chain. Exact versions, a 7-day minimum release age, a dependency audit in CI, and container images pinned by digest.
  • No tracking. No analytics, no cookies, no account. The theme and the watchlist live in the visitor's own browser.

Built for people and for machines

  • Accessible: WCAG 2.2 AA, checked by an automated audit in the end-to-end tests; keyboard reachable, with live announcements as the page changes.
  • For search engines: server-rendered pages with structured data, canonical URLs and sitemaps of every CVE.
  • For programs and agents: the JSON API with an OpenAPI description, an Atom feed for any filter, every CVE as Markdown, llms.txt, an API catalog (RFC 9727), and WebMCP tools an agent in the browser can call.

Running it yourself

docker compose up -d --build starts the whole site; DEPLOY.md walks through a server behind a Cloudflare Tunnel step by step, and deploy/update.sh installs a newer version. The first sync takes about an hour without an NVD key, minutes with a free one. Everything is MIT licensed.