
Offline-first vulnerability findings tracker that searches 11 CVE databases in parallel, adds EPSS/KEV enrichment, and manages coordinated disclosure deadlines.
A static, client-side vulnerability findings tracker built with Astro + React — also packaged as a native Linux desktop app (Tauri v2). Log security findings, track them through coordinated disclosure, search 11 live vulnerability databases in parallel, and browse the latest CVEs — in your browser or as a desktop app, with no backend required.
localStorage (no server, no account)The tracker (/tracker) is a single-page app with five tabs:
The management view for your logged findings:
Query all enabled databases in parallel with one query string:
Zero-input browsing of the latest vulnerabilities:
The control panel for database connectivity:
.env values{query} placeholder, optional bearer key), test it, or remove it; connected custom databases appear as extra chips in the Search viewA Finding is the core record. Fields:
Validation is enforced in the form: required fields, URL format, description length, and CVSS range.
11 databases integrated, plus EPSS enrichment. Seven connect directly with no key; four need a credential from .env.
Not integrated (with reasons): Snyk (org-scoped, no public search API), JFrog Xray (self-hosted, returns artifacts not CVE records), Trivy (CLI scanner, no REST API), MSRC (monthly CVRF documents, not keyword-searchable), VulnCheck (bulk backup endpoint only), Debian/Ubuntu (multi-GB dumps, not client-searchable), Exploit-DB/Sploitus (no public API), CNVD/CNNVD (manual XML downloads), Sonatype OSS Index (purl/component-based, not keyword).
Design system: the "Dark Sunset Boulevard" theme — deep purple-tinted dark surfaces (ink-950 #1A2226, ink-900 #223036, ink-800 #264653), warm sand text (ink-100 #F2EAE0, ink-400 #A49A8C), and a burnt-orange accent (#E76F51). All colors are Tailwind 4 tokens defined in src/styles/global.css — no hardcoded hex in components.
Requires Node.js >= 20.
# Install dependencies
npm install
# Start the dev server (http://localhost:4321)
npm run dev
# Type-check (Astro + TypeScript)
npm run check
# Production build (outputs to dist/)
npm run build
# Preview the production build
npm run preview
The build produces a fully static site (dist/) — deployable to any static host (GitLab Pages, Netlify, Cloudflare Pages, GitHub Pages, nginx…). The same build is also wrapped into a native desktop app — see Native Desktop App.
One codebase, every delivery format. The same source builds the web site, desktop apps, and an Android app — there are no separate web/app branches. The apps are the static build wrapped in a small Tauri v2 Rust shell that renders it in the system webview (WebKitGTK on Linux, WKWebView on macOS, WebView2 on Windows, Android WebView). No server, no browser tab; settings and findings persist in the app's own data directory.
The shipped desktop bundles target Linux (.deb, .rpm, AppImage).
System dependencies (Debian/Ubuntu):
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev
Develop (hot-reload: Astro dev server + Tauri window):
npm run tauri:dev
Build installable bundles (.deb, .rpm, AppImage — output in src-tauri/target/release/bundle/):
npm run tauri:build
The Rust shell is intentionally minimal — zero IPC commands in v1; all logic stays in the web layer, so the browser build and the desktop app behave identically.
macOS desktop support is not enabled in the default config — the shipped bundles are Linux-only. The codebase itself is fully cross-platform (the web layer runs in any browser, and the Rust shell has zero IPC commands), so enabling macOS is a small config change plus a build on a Mac. See docs/macos-development.md for the full guide: prerequisites, enabling the dmg target, icons, signing & notarization, and a GitLab CI example.
Windows desktop support is not enabled in the default config — the shipped bundles are Linux-only. The codebase is fully cross-platform, and Windows builds run on a Windows 11 machine with the MSVC toolchain (WebView2 is preinstalled). See docs/windows-development.md for the full guide: prerequisites, enabling the nsis / msi targets, icons, code signing & SmartScreen, and a GitLab CI example.
The Android target is not initialized in the repo — one command scaffolds it (npm run tauri android init). The codebase is fully cross-platform (the web layer runs in Android WebView, and the tracker UI is already responsive for phones). See docs/android-development.md for the full guide: prerequisites (Android Studio, SDK, NDK, JDK 17), initializing the target, building APKs/AABs, and a GitLab CI example.
Four search sources require credentials. Copy .env.example to .env, fill in the keys, and rebuild:
Security note: this is a static site —
PUBLIC_*variables are inlined into the client bundle at build time and visible in the page source. Use low-privilege keys, or a server-side proxy for real secrecy. Sources without a key simply show an error chip; the rest of the search still works.
Tip: you can also enter these keys in the app's Settings tab — runtime keys override build-time
.envvalues, no rebuild needed.
.env files are gitignored; only .env.example is committed.
Database on/off switches and API keys are managed at runtime from the Settings tab — no file editing or rebuild needed. The defaults live in src/tracker/lib/dbConfig.ts, and runtime overrides are stored in the app (localStorage, key settings:all).
Precedence: runtime settings (Settings tab) > .env build-time values > dbConfig.ts defaults.
Each config entry has:
enabled — the default state; the Settings tab can override it per sessionsearch / browse — which views the source participates inneedsKey + keyVar — marks sources that read a credential (from .env or the Settings tab)The UI is fully derived from the effective config: getSearchSources() (Search chips) and getBrowseSources() (Browse chips) are filtered to enabled sources, and the adapters are filtered at query time — toggling a source off in Settings removes it from the UI and from queries immediately.
Custom databases are stored in the same runtime settings (customDbs). Each entry has a name, a URL (optionally containing a {query} placeholder — otherwise the query is appended as ?q=), and an optional bearer key. They are search-only: they appear as extra chips in the Search view and are queried in parallel with the built-in sources. Responses are accepted as CVE JSON 5.0 ({ "vulnerabilities": [{ "cve": ... }] }) or a simple array of records with flexible field names.
All database access lives in src/tracker/lib/, split by responsibility (dbSearch.ts is a barrel that re-exports the previous public surface):
dbFetch.ts — fetch-with-timeout helper + shared constantsdbAdapters.ts — the 11 built-in search adapters + record parsersdbBrowse.ts — the 4 browse adapters + browseDatabases pipelinedbCustom.ts — the user-configured custom database adapterdbPipeline.ts — merge / KEV / EPSS machinery + searchDatabasesquery ──► Promise.all over enabled adapters ──► normalizeDbItem
──► mergeDbItems (dedup by CVE / ID / title, cross-merge)
──► attachKevBadges (CISA KEV catalog, cached)
──► enrichWithEpss (FIRST EPSS API, batched by CVE)
──► sorted results + per-source statuses + timing
RawDbItems. Keyed adapters throw a clear "no API key configured" error when the credential is missing.api.first.org; each card shows the EPSS score and percentile.The Browse pipeline is the same merge/KEV/EPSS machinery over the four browse sources, with pagination (hasMore when any source returned a full page).
Offline mode swaps the live pipeline for the downloaded snapshot: Search and Browse read the IndexedDB cache (src/tracker/lib/offline.ts), skip EPSS enrichment, and attach KEV badges only when the CISA KEV feed was downloaded too. Sources without a downloaded snapshot report an "error / not downloaded" chip instead of hanging. Custom databases are queried through the same adapter interface — the URL's {query} placeholder is substituted (or ?q= appended), an optional bearer key is sent as Authorization, and responses are normalized from CVE JSON 5.0 or a simple array.
Findings are stored in localStorage under the key findings:all — no server, no account, works offline.
Data lives in the browser profile. Clearing site data wipes your findings — export to CSV/Markdown first if you need a backup.
Offline snapshots live in IndexedDB (database vulnbook-offline, stores items + meta) — downloaded per source from the Settings tab and used by Search/Browse while offline mode is on. They survive reloads and are removed only when you click Remove for that source or clear site data.
From the Findings view, export the currently filtered and sorted list:
Files are named findings-YYYY-MM-DD.csv / .md and downloaded automatically.
The built-in AI assistant inside the tracker — an overlay sidebar that slides over the content without reflowing it. See docs/ai-chatbox.md for the full guide (provider setup, security notes, troubleshooting).
localStorage (chat:config), sent only to the provider's own endpoint/ in the composer: /mcp and /skill open the AI Marketplace, /new starts a conversation, /clear empties it, /help lists commandsSecurity: keys stay local and go only to the provider; assistant markdown is rendered through a strict sanitizer (no raw HTML). MCP tools execute in the browser against the app's own data and public APIs only — each tool validates its arguments, runs under a 12-second timeout, and truncates output to 4,000 characters; no arbitrary code runs.
The project has no unit test framework (established decision) — verification is a three-step gate:
# 1. Type-check
npm run check
# 2. Build
npm run build
# 3. E2E smoke test (Playwright, 16 checks)
npx http-server dist -p 8899 -a 127.0.0.1 &
NODE_PATH=<path-to-playwright-node_modules> node scripts/smoke.cjs
The smoke test (scripts/smoke.cjs) covers: landing page render + hydration, tracker island hydration, finding CRUD, localStorage persistence across reload, list view + export controls, detail modal (open/Escape), deletion, database search producing result cards, the browse view producing result cards, the settings tab (toggling a source off removes its chip, re-enabling restores it), offline mode (toggle shows the offline banner in Search and restores live mode), and custom database form validation (invalid URL rejected).
PLAYWRIGHT_CHROMIUM_PATH or a default cache pathFor native-app changes, additionally run npm run tauri:build (see Native Desktop App).
├── src/
│ ├── components/ # Shared Astro components + animation primitives
│ │ └── anim/ # Skeleton, Spinner, FadeInUp, CountUp
│ ├── layouts/ # SiteLayout (fonts, meta, theme)
│ ├── pages/ # index.astro (landing), tracker.astro, 404.astro
│ ├── styles/ # global.css — Tailwind 4 design tokens
│ └── tracker/
│ ├── components/ # Tracker UI
│ │ ├── TrackerApp.tsx # Shell: tabs, modals, state
│ │ ├── Dashboard.tsx # Stats + charts + deadlines
│ │ ├── FindingsList.tsx / FilterBar.tsx
│ │ ├── DbSearchView.tsx / BrowseView.tsx / SettingsView.tsx
│ │ ├── FindingFormModal.tsx / FindingDetailModal.tsx
│ │ ├── ConfirmDeleteDialog.tsx / EmptyState.tsx / ErrorBanner.tsx
│ │ ├── SourceChip.tsx / SearchResultCard.tsx / StatCard.tsx / badges.tsx
│ │ ├── AiMarketplace.tsx # AI Marketplace grid (MCP/Skill cards, tool counts, enable toggles)
│ │ └── chat/ # AI ChatBox UI
│ │ ├── AiChatSidebar.tsx # Overlay sidebar + the tool-calling agent loop
│ │ ├── ChatMessageList.tsx # Renders messages incl. inline tool calls
│ │ ├── ChatComposer.tsx / ChatConfigPanel.tsx / ChatHistoryList.tsx
│ └── lib/ # Logic
│ ├── types.ts # Finding, DbItem, KevEntry, View, …
│ ├── constants.ts # Severity/status order + styles, button classes
│ ├── dbConfig.ts # ← per-source defaults (on/off, key vars)
│ ├── settings.ts # ← runtime settings store (Settings tab overrides)
│ ├── offline.ts # IndexedDB offline snapshot store (download/remove/query)
│ ├── dbSearch.ts # barrel: re-exports the db layer (see "How the Search Pipeline Works")
│ ├── dbFetch.ts # fetch-with-timeout helper + shared constants
│ ├── dbAdapters.ts # 11 built-in search adapters + record parsers
│ ├── dbBrowse.ts # browse adapters + browseDatabases pipeline
│ ├── dbCustom.ts # user-configured custom database adapter
│ ├── dbPipeline.ts # merge/KEV/EPSS pipeline + searchDatabases
│ ├── findings.ts # CRUD + validation
│ ├── storage.ts # localStorage + artifact-API fallback
│ ├── export.ts # CSV / Markdown export
│ ├── format.ts # date/URL/id helpers, cvssToSeverity
│ └── chat*.ts # AI ChatBox logic (see "AI ChatBox"): chatStore (config/history),
│ # chatProvider (3 wire families + tool calling), chatTools (tool
│ # registry + in-browser MCP runtime), chatMcp (enabled-state),
│ # chatCatalog / chatKnowledge / chatFindings / chatCommands / chatMarkdown
├── scripts/smoke.cjs # Playwright E2E smoke test (16 checks)
├── docs/ # Developer guides (macOS, Windows 11, Android development)
├── src-tauri/ # Tauri v2 native shell (Rust, minimal)
├── research/ # Vulnerability database & tooling research notes
├── .env.example # Documented API-key template
└── astro.config.mjs
The research/ directory contains the background research that drove the database integrations:
vulnerability-databases.md — ~60 databases catalogued (access model, API availability, caveats), including the NVD enrichment changes of Apr 2026, EUVD/OpenCVE/VulDB API details, and the "why not" listvulnerability-finding-tools.md — the broader vulnerability-finding tooling landscape (SAST/SCA/fuzzing/pentest) and a solo-practitioner pipelineThis project follows a design → plan → execute workflow.
New features start with a design doc, get validated, then planned and executed in micro-tasks. See CONTRIBUTING.md for the full conventions (branching, verification gate, commit style, accessibility rules).
MIT © 2026 T. Beckett
| Field | Description |
|---|
id | Unique identifier |
projectName | Project the finding belongs to (required) |
repoUrl | Repository URL (required, must be a valid URL) |
title | Short title (required) |
description | Detailed description (required, ≥ 10 characters) |
severity | Critical / High / Medium / Low |
cvssScore | CVSS score 0–10 (optional) |
cweId | CWE identifier, e.g. CWE-79 |
cveId | CVE identifier, e.g. CVE-2021-44228 |
affectedVersions | Affected version ranges |
status | New / Reported / Acknowledged / Fixed / Disclosed |
discoveryDate | When you found it |
reportedDate | When you reported it to the vendor |
disclosureDeadline | Coordinated-disclosure deadline (drives the dashboard alerts) |
referenceLinks | List of reference URLs |
notes | Free-form notes |
tags | List of tags (used for filtering) |
createdAt / updatedAt | Timestamps |
| # | Source | Access | Search | Browse | Notes |
|---|
| 1 | NVD (NIST) | Direct | ✅ | ✅ | NVD API 2.0; 30-day window in Browse |
| 2 | CIRCL | Direct | ✅ | ✅ | CVE search + "last" feed (OSV/CSAF shapes) |
| 3 | GitHub Advisories | Direct | ✅ | ✅ | GHSA API |
| 4 | OSV (Google) | Direct | ✅ | — | Open-source ecosystem vulnerabilities |
| 5 | CISA KEV | Direct | ✅ | ✅ | Known Exploited Vulnerabilities catalog |
| 6 | Red Hat | Direct | ✅ | — | Red Hat CVE database |
| 7 | EUVD (ENISA) | Direct | ✅ | — | European Vulnerability Database |
| 8 | Vulners | .env key | ✅ | — | PUBLIC_VULNERS_API_KEY |
| 9 | MEND | .env token | ✅ | — | PUBLIC_MEND_API_TOKEN (CVE ID lookup) |
| 10 | VulDB | .env key | ✅ | — | PUBLIC_VULDB_API_KEY (credit-based) |
| 11 | OpenCVE | .env token | ✅ | — | PUBLIC_OPENCVE_API_TOKEN |
| — | EPSS (FIRST) | Direct | — | — | Enrichment only: scores CVEs 0–1 + percentile |
| Layer | Choice |
|---|
| Framework | Astro 6 (static output) |
| UI | React 19 islands |
| Styling | Tailwind CSS 4 (token-driven classes) |
| Charts | Recharts |
| Icons | lucide-react |
| Motion | framer-motion |
| Desktop | Tauri v2 (Linux: deb / rpm / AppImage; Windows: see Windows 11 Development; macOS: see macOS Development) |
| Mobile | Android via Tauri v2 — see Android Development |
| AI Chat | Built-in chatbox — OpenAI / Claude / Gemini / Ollama / OpenCode Zen (see AI ChatBox) |
| Language | TypeScript (strict) |
| Source | Variable | Where to get it |
|---|
| Vulners | PUBLIC_VULNERS_API_KEY | Vulners account → API keys |
| MEND | PUBLIC_MEND_API_TOKEN | MEND SCA API 2.0 (JWT bearer token) |
| VulDB | PUBLIC_VULDB_API_KEY | VulDB account → API key (credit-based) |
| OpenCVE | PUBLIC_OPENCVE_API_TOKEN | OpenCVE organization → API tokens |
chat:history, capped with oldest-first trimming)