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/coreis 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 GitHubTech stack
| Layer | Technology | Why |
|---|---|---|
| Language | TypeScript 7, Node 24 | One language end to end; the contract is one set of types |
| Monorepo | pnpm 12 workspaces, Turborepo | Exact versions, every dependency at least 7 days old before it installs; cached builds and tests |
| API | Hono, zod | Small and fast, standard Request/Response; inputs and outputs validated |
| Storage | node:sqlite with FTS5 | A full database and full-text search built into Node: no native module, no database server to run |
| Logging | pino | Structured JSON logs with a request id on every line |
| Web framework | React 19, TanStack Start, Router, Query, Table, Charts | Server rendering, the whole filter in the URL, typed routes, cancellable data loading |
| Documents | TanStack Markdown and Highlight | The API reference, changelog and this page rendered from docs/ at build time |
| Server | Nitro | The frontend's Node server, with precompressed assets |
| UI | Tailwind CSS 4, Base UI, Lucide | Design tokens for light and dark themes; accessible primitives for selects and suggestions |
| Images | satori, resvg | Share images of each CVE drawn on the server, no browser needed |
| Quality | oxlint, oxfmt, Vitest, Playwright, axe-core | Lint and format in milliseconds; unit, end-to-end and accessibility tests on every change |
| Hosting | Docker Compose, Cloudflare Tunnel, Hetzner Cloud CX23 | One 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:
| Source | What it adds | How it is read | Every |
|---|---|---|---|
| CVE List (cvelistV5) | Every record, CNA scores, CISA's Vulnrichment | The whole repository once as an archive, then only what its delta log lists | 10 min |
| NVD API 2.0 | CVSS and CWE where the record has none | Every CVE once, then only what NVD changed since the last read | 1 hour |
| CISA KEV | Known exploitation, ransomware use, deadlines | One JSON file; CISA's GitHub copy when cisa.gov refuses | 6 hours |
| FIRST EPSS | Probability of exploitation in the next 30 days | One compressed CSV of every score, swapped in whole | 6 hours |
| GitHub Advisory Database | Affected packages and their ecosystems | The reviewed advisories from the repository's archive | 12 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
503at 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.