<!-- Copyright (c) 2026 Pierre Gronau, ndaal in Cologne -->
# nvulnlookup
**Vulnerability-Lookup rewritten in Rust** by
[ndaal](https://ndaal.de) -- information security
& compliance, Cologne.
A single-binary vulnerability aggregation platform
that correlates vulnerabilities from 189 feeders
(154 data sources), independent of vulnerability IDs.
## Features
- **189 feeders** (154 data sources): 78 CSAF
providers (pure Rust downloader), 15 OSV
ecosystems, NVD, CISA KEV, CNVD, CERT-FR,
JVNDB, GitHub, Tailscale, ZDI, Exploit-DB,
GCVE, enrichment feeds (EPSS, CWE, CAPEC, GCVE,
Nuclei, Tsunami, Dashboard stats, CVE Forecast,
cve.icu Analytics), Moksha (moksha.dk
self-issued advisories, cluster enrichment,
since v0.1.47), ndaal KEV (deduplicated union of
CISA + CIRCL + EUVD KEV, recent Chromium/Firefox
advisories, and the ndaal public KEV feed)
- **Annotation system**: Sightings (8 types),
KEV catalog (BCP-07 with evidence), in-app
notifications with pattern matching
- **Single binary**: No external databases needed
(embedded redb + SQLite)
- **Storage compression**: Brotli blob compression
(`vl-codec`) on the redb + SQLite stores, on by
default (disable with `VL_DISABLE_COMPRESSION=1`);
reads auto-detect legacy data, secret columns
(pwdhash/token/apikey/feedkey) never compressed
- **TLS 1.3**: Dual listeners -- TCP:8080 (HTTPS)
and UDP:8081 (QUIC/HTTP3)
- **Real-time feeding**: Background feeders pull
data continuously (NVD, CISA KEV, CSAF, git)
- **Bulk import**: 75 CIRCL NDJSON dump sources
for fast initial seeding
- **Web UI**: Dark/light theme, severity badges,
CVSS scores, KEV catalogs, sightings page,
annotations hub, source filtering
- **HATEOAS API**: REST API with hypermedia links
- **Enrichment**: EPSS, KEV, CWE, CAPEC, ATT&CK,
GCVE, Nuclei, Tsunami
- **Search**: Embedded full-text search by CVE ID,
keyword, or description
- **Feeds**: RSS and Atom output
- **Performance**: <4 cores, <8 GB RAM target
## What's new in v1.2.61
- **Boot-collapse availability fixes** — the
homepage (`GET /`) and the startup seed
check no longer run an O(corpus) scan (both
are O(1) now), and the CWE/CAPEC offline
archives resolve at runtime, so a cross-built
binary deployment no longer 504s under the
boot feeder burst or loses CWE/CAPEC
enrichment (RCA C1/C5/C8).
- **Operator-tunable limits** — the codec
decode cap (`VL_CODEC_MAX_DECODE_BYTES`,
default 64 MiB) and the per-call search
budget (`VL_SEARCH_BUDGET_SECS`, default
10 s) are now read from the environment
instead of hard-coded, so a large record or
a slow search back-end is handled without a
rebuild.
- **Enrichment API error shape** — an unknown
enrichment `{source}` slug now returns an
RFC 9457 `problem+json` 400 (code
`unknown_source`) instead of a bare
`{"error":…}` that echoed the slug.
- **Meilisearch install scripts** —
`scripts/install_meilisearch_on_macos.sh` and
`scripts/install_meilisearch_on_debian.sh`
install a pinned, SHA-256-verified
Meilisearch for the optional full-text search
back-end (the Debian one verified end-to-end
on a real Debian 13 box).
- **Hardened API test suite** — every Bruno
collection now passes against both an empty
and a populated store, with new CWE-404 route
coverage.
## What's new in v1.2.60
- **Brotli compression on by default** — the
embedded redb and SQLite stores now compress
freshly written blob values with Brotli (via the
shared `vl-codec` crate) above a 256-byte
threshold. Reads auto-detect the frame, so
existing databases keep working unchanged; opt
out with `VL_DISABLE_COMPRESSION`.
## What's new in v1.2.56
- **CVE Forecast dashboard** (`/cve-forecast`) — mirrors
[CVEForecast.org](https://cveforecast.org)'s daily
per-CNA prediction JSON, refreshed hourly.
- **cve.icu Analytics dashboard** (`/cve-icu`) — mirrors
all 46 of [cve.icu](https://cve.icu)'s per-document
analytics JSON (growth, scoring, CWE, CPE, calendar,
per-year and summary), refreshed every 6 hours.
- **SSVC decision-tree solver** — CVE detail pages now
show a "Recommended action" card (Track / Track\* /
Attend / Act), computed from the CISA-Coordinator
v2.0.3 decision table against the CVE's parsed SSVC
decision points.
- **Navbar reorganised**: the "Stats" link is now a
"Statistics" dropdown grouping the per-source count
page (relabelled "Sources") with the two new
dashboards above.
- **ndaal branding icons** replaced with the logo mark on
the `/kev?catalog=ndaal` tab, the `/dashboards/ndaal`
dashboard header, and its navbar submenu entry --
instead of the generic Bootstrap icons shown before.
- **Frontend lint sweep** across all 60 web templates:
a broken `.oxlintrc.json` (three dead `node/*` rules
were silently failing the entire config), 283 htmx
false positives in markuplint (missing
`@markuplint/htmx-spec`), and several real template
issues (inline `onclick` handlers, an inline style, a
disallowed nested element) fixed.
- **Six git-based feeders migrated to a bare-mirror sync**
(FKIE NVD, CVEListV5, GSD, GitHub Advisories, OSSF
Malicious Packages, Vulnrichment) -- fixes a two-month
bug where four of them imported zero rows, and removes
the disk I/O contention it caused for every other feeder.
- **Dashboards dropdown fix, properly verified this time**:
the Linux, BSD, BSI, Browser and CPE categories no longer
get clipped on laptop-size screens.
- **`htmlhint` clean on the app shell** -- every page now
carries a `<meta name="description">` tag.
- **`cargo clippy --workspace` clean** again -- a stale
dependency allowlist had let it go red.
> Note: this crate-level README's "What's new" list skipped
> v0.1.51–v0.1.55 (and the version-scheme bump to 1.2.x);
> see the root [`CHANGELOG.md`](../CHANGELOG.md) for the
> complete history.
## What's new in v0.1.50
- **Bash CLI-options standard + anti-fragile `print_script_info` footer**
(`skills/bash`): the five canonical options every option-bearing script must
offer (`-h`, `-V`, `-v`, `-q`, `-l FILE`), and the footer fix that reuses the
absolute `${SCRIPT_PATH}` / `${SCRIPT_NAME}` anchors — applied across 30
scripts (incl. two `#!/bin/sh` → bash and two bare-bash conversions; each
green against its sibling bats).
- **Per-source ndaal dump export/import + accessibility exit-code fix**:
`export_sources_ndaal_dumps.sh` / `import_sources_ndaal_dumps.sh` export and
re-import each source as verified NDJSON (schema, entry count, 25-entry DB
compare, full 5-sidecar contract, SARIF 2.1.0 + Markdown reports); and
`verify-pdfs.sh` / `run-mobile-a11y.sh` now fail the build when a PDF/UA or
mobile a11y gate fails.
- **`sadp_pilot` feeder + `/dashboards/sadp`**: a new feeder for the CVE
Program Supplier-ADP (SADP) Pilot git-clones the repo on a 12-hour cycle and
imports the CVE 5.2 records under `Published SADP Records/` (suppliers'
VEX-like product status). A hand-listed `/dashboards/sadp` page and a
"SADP — Supplier VEX / Status" enrichment card on the aggregated CVE view
surface the supplier ADP entries.
- **`ssvc` feeder + dedicated `/dashboards/ssvc`**: a new feeder for the
CERT/CC SSVC framework git-clones the repo on a 12-hour cycle and parses
`ssvc_object_registry.json` into a methodology dashboard (decision points,
values, and outcome groups), hand-listed under "Enrichments & charts".
Per-CVE SSVC scoring already appears on each CVE detail page.
## What's new in v0.1.49
- **QA "gate factory" — 9 new `--fast` static-analysis gates** (102–110):
`linthis`, `pyscan`, `pip_audit`, `lineguard`, `x509lint`,
`cargo_workspace_lints`, `cargo_unused_workspace_deps`, `cargo_fl`, and
`script_footer_guard`. Each tool runner is ndaal-canonical, emits every native
output format plus a jaq-synthesised SARIF 2.1.0 report, self-skips with
`rc=0` when its tool is missing, and ships a sister `*.bats`. All wired into
`scripts/quality_gates.sh --fast`.
- **`script_footer_guard` (gate 110) + repo-wide footer sweep**: fixed the
`print_script_info` footer across all ~197 tracked `.sh` files to derive its
path from the absolute `${SCRIPT_PATH}` captured before any `cd`, instead of
re-deriving it from a relative `${BASH_SOURCE[0]}` (which leaked
`cd: … No such file or directory` and a bogus path when a script `cd`d mid-run
and was invoked by a relative path). A new bats guard fails the build on any
regression.
- **`create_sbom_with_cargo_sbom.sh`** generates an SPDX 2.3 and a CycloneDX 1.6
SBOM via `cargo-sbom`, augmented with the release-tree binaries / `.deb` /
`.rpm` / tarballs (SHA-256 / SHA-512 / SHA3-512 / BLAKE3 hashes). Wired into
`release/create_release.sh` after the build loop.
- **MSRV bump 1.93.0 → 1.95.0** (`sysinfo` 0.39.2 requirement), plus a
rust-doctor + frontend-lint cleanup (clippy `--fix`, `osv_bulk` buffer reuse,
10 dashboard templates modernised so htmlhint / oxlint / fta run clean).
> Note: this crate-level README's "What's new" list skipped v0.1.46–v0.1.48;
> see the root [`CHANGELOG.md`](../CHANGELOG.md) for the complete history.
## What's new in v0.1.45
- **Dashboards dropdown rewrite** — the navbar
Dashboards menu used to render as a tall
single-column list on 1366 px laptops because the
pre-v0.1.45 CSS pinned `min-width: 100rem` inside
a `@media (min-width: 992px)` block. Replaced
with a fluid declaration —
`width: clamp(13rem, calc(100vw - 2rem), 120rem)` +
`column-width: 10rem` — so the browser
picks the column count per viewport (1 col phone
→ 8 cols laptop → 11 cols desktop). 7 navbar
regression tests pin the new contract.
- **`Cache-Control: no-store` on every HTML
response** — fixes the "rebuilt server still
shows old page" trap. Static assets under
`/static/` keep their own caching.
- **Two new test-orchestrator scripts** —
`tests/scripts/test_html.sh` (htmlhint × 8 +
oxlint × 10 + fta × 3 formats per run) and
`tests/scripts/test_fuzzing_targets.sh` (full
85-target cargo-fuzz sweep with SARIF 2.1.0
output, CI-security-tab compatible).
- **Six new quality gates** — `9r gitleaks_scanner`,
`9s opengrep`, `9t html_linters`,
`9u endpoint_lists_drift` (read-only drift
detector), `9v feed_recent_25` (LIVE — recent-25
probe over every live source), and
`57a fuzzing_targets` (LIVE — full cargo-fuzz
sweep).
## What's new in v0.1.44
- **`/dashboards/gsd` (NEW)** — Global Security
Database (Cloud Security Alliance). Note: this
data source is no longer maintained upstream
(last update 2024-04-29); the page renders a
warning banner so operators see the staleness
at a glance. Raw passthrough at
`/api/v1/gsd/{key}`.
- **Six new quality gates** — `cargo_fa`,
`cargo_propagate_features`, `cargo_reedme`,
`cargo_describe`, `cargo_auditable` (all in the
`--fast` profile) plus `cargo_mend` (`--full`
only). `ALL_GATES` in `scripts/quality_gates.sh`
goes from 92 to 98.
## What's new in v0.1.42
- **EPSS feeder data-source fix** — the enrichment
feeder previously fetched a community KEV∩EPSS CSV
(~1 200 CVEs) and hard-coded `kev: true` for every
imported row. It now pulls the official FIRST.org
bulk dataset (~300 000+ entries, gzip) and
cross-references CISA KEV so `kev` is set if and
only if the CVE is currently on the KEV list
(advisory `ndaal-sa-2026-038`).
- **`VulnStorage::new_in_memory()`** — new constructor
on top of `redb::backends::InMemoryBackend`, plus an
extracted `init_tables(&Database)` so the 18-table
schema lives in exactly one place. ~130 test call
sites simplified across 16 files
(advisory `ndaal-sa-2026-037`).
- **5 missing role templates** — the
`auditd_rules_reload` plus `auditd_dynamic_root_rules`
`.service` / `.timer` / `.sh` trio under
`tools/ansible/roles/nvulnlookup/templates/etc/systemd/`,
modeled on the role's hardened-systemd profile
(NoNewPrivileges, ProtectSystem=strict,
MemoryDenyWriteExecute, capability-bounded set).
- **Per-test toggles + 53 new playbooks** in both
Ansible role `tests/test.yml` masters; every
`import_playbook` is now guarded by
`nvulnlookup_run_test_<slug>` so an operator can
suppress any subset with
`-e nvulnlookup_run_test_<slug>=false`.
- **3 new Administrator Guide chapters** — Build
Release (3-stage release pipeline + 4 verification
gates), Bash Script Inventory (115 scripts with
Purpose / Usage / verbatim header description), and
Quickstart: Filling the Database
(`fetch_dumps.sh → compress_dumps.sh →
import_and_verify_dumps.sh`).
- **Release rebuild** — 8 binaries (`vl-web` +
`vl-cli` × 4 triples), 4 tarballs, 12 per-binary
sidecars (sha-256 / sha-512 / sha3-512), 3
aggregate manifests, plus a 72-sidecar refresh
across both Ansible role `files/` trees.
- **Per-CVE enrichment cards + bundle / per-source
JSON endpoints** — `/vulnerability/{id}` renders
one Bootstrap card per available enrichment
source (14 sources). New endpoints:
`/api/v1/vulnerability/{id}/enrichment` (bundle)
and `/api/v1/vulnerability/{id}/enrichment/{source}`
(per-source).
- **`/dashboards/cve-vs-github` (NEW)** — CVSS
v3 score deviations between GitHub Advisory and
canonical CVE source. Raw passthrough at
`/api/v1/cve-vs-github/{key}`.
- **`/dashboards/kev-ransomware` (NEW)** — silent
CISA KEV `knownRansomwareCampaignUse` flips via
Greynoise. Raw passthrough at
`/api/v1/kev-ransomware/{key}`.
- **`/sightings` drill-down 200 fallback** — every
CVE listed on `/sightings` now renders 200 (was
404) when not yet in the canonical
vulnerabilities table; placeholder template
surfaces enrichment cards.
- **Imprint page + 14 German alias redirects** —
`/imprint`, `/impressum`, `/datenschutz`, etc.
- **Reference impl Log4Shell demo** — 26-call
end-to-end pass exercising every v0.1.42+
surface; 13 new pytest classes (151 total).
- **Dependency hygiene** —
`cargo outdated --workspace`: all deps at
latest available version.
See [`CHANGELOG.md`](CHANGELOG.md) for the full list.
## Quick Start
```bash
# Build
cargo build --release -p vl-web --features quic
# Run (starts on https://localhost:8080)
./target/release/vl-web
# Optional: import CIRCL dumps for bulk data
bash scripts/fetch_dumps.sh
cargo run -p vl-cli -- import-dumps --dir dumps
```
## API Endpoints
### Vulnerability
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/api/v1/vulnerability/{id}` | Get vulnerability |
| GET | `/api/v1/vulnerabilities/recent` | Recent vulnerabilities |
| GET | `/api/v1/vulnerabilities/sources` | List sources |
| GET | `/api/v1/vulnerabilities/search` | Search |
### Enrichment
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/api/v1/vulnerability/{id}/enrichment` | All enrichment |
| GET | `/api/v1/vulnerability/{id}/cwe` | CWE mapping |
| GET | `/api/v1/vulnerability/{id}/capec` | CAPEC/ATT&CK |
| GET | `/api/v1/vulnerability/{id}/gcve` | GCVE data |
| GET | `/api/v1/vulnerability/{id}/cvss` | CVSS scores |
### Annotations
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/api/v1/sightings` | List sightings |
| GET | `/api/v1/sightings/{vuln_id}` | Sightings for vuln |
| POST | `/api/v1/sighting` | Create sighting |
| GET | `/api/v1/kev` | List KEV entries |
| GET | `/api/v1/kev/{uuid}` | KEV detail + evidence |
| POST | `/api/v1/kev` | Create KEV (BCP-07) |
| GET | `/api/v1/notifications` | List notifications |
| POST | `/api/v1/notification/{uuid}/read` | Mark read |
### System
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/api/v1/system/health` | Health check |
| GET | `/api/v1/system/info` | System info |
| GET | `/api/v1/dashboard/data` | Dashboard data |
### Debian Security Tracker (`/dashboards/debian`, since v0.1.31)
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/dashboards/debian` | Dashboard page |
| GET | `/dashboards/debian/recent/partial?page=N` | HTMX partial — next 25 rows + self-replacing sentinel |
| GET | `/dashboards/debian/cve/{id}` | Per-CVE drill-down (merged view + raw Debian note) |
| GET | `/api/v1/debian/{key}` | Raw aggregate JSON (`stats`, `packages_top`, `releases`, `recent`, `recent_all`) |
### CWE dashboard (`/attacks/cwe`, since v0.1.36)
Surfaces MITRE Common Weakness Enumeration occurrences across the
stored vulnerability corpus. Catalog is loaded from the bundled
`dumps_archive/cwec.7z` (MITRE NDJSON dump, 969 weaknesses) by the
real `CwecFeeder`; counts come from the existing `ENRICHMENT_CWE`
enrichment. The aggregator joins both and writes five pre-computed
JSON blobs, so the dashboard page renders O(1) regardless of corpus
size.
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/attacks/cwe` | Dashboard page (stats + top weaknesses + categories + recent) |
| GET | `/attacks/cwe/recent/partial?page=N` | HTMX partial — next 25 rows |
| GET | `/attacks/cwe/cwe/{id}` | Per-CWE detail page (accepts `CWE-79`, `cwe-79`, or `79`) |
| GET | `/api/v1/cwe/{key}` | Raw aggregate JSON (`stats`, `cwes_top`, `categories`, `recent`, `recent_all`) |
### Vendor dashboards (`/dashboards/<slug>`, since v0.1.34)
Fifteen vendor dashboards parallel to the Debian one, driven by a
single generic handler (`routes::vendor_dashboard`) plus a
`VENDOR_SPECS` static table. Each dashboard shows the Last 25
recent entries for its source with HTMX "Load next 25" pagination
and a per-advisory drill-down.
| Slug | Storage source | Display name |
| ------ | ---------------- | -------------- |
| `cert-bund` | `csaf_certbund` | CERT-Bund |
| `cisco` | `csaf_cisco` | Cisco |
| `github` | `github` | GitHub |
| `go` | `osv_golang` | Go |
| `microsoft` | `csaf_microsoft` | Microsoft |
| `ndaal` | `csaf_ndaal` | ndaal |
| `opensuse` | `csaf_opensuse` | OpenSUSE |
| `oracle` | `csaf_oracle` | Oracle |
| `pentagrid` | `csaf_pentagrid` | Pentagrid |
| `python` | `pysec` | Python |
| `redhat` | `csaf_redhat` | Red Hat |
| `rust` | `osv_rustsec` | Rust |
| `suse` | `csaf_suse` | SUSE |
| `ubuntu` | `osv_ubuntu` | Ubuntu |
| `vmware` | `csaf_vmware` | VMware |
Route shapes:
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/dashboards/<slug>` | Dashboard page |
| GET | `/dashboards/<slug>/recent/partial?page=N` | HTMX partial — next 25 rows |
| GET | `/dashboards/<slug>/<advisory_id>` | Per-advisory drill-down (uses the merged `/vulnerability/{id}` view) |
| GET | `/vulnerability/<vendor>/<advisory_id>` | Alternate URL shape; same content as the drill-down |
### GSD dashboard (`/dashboards/gsd`, since v0.1.44)
Global Security Database (Cloud Security Alliance).
**This data source is no longer maintained upstream
— last update 2024-04-29.** The dashboard renders a
prominent warning banner so operators see the
staleness at a glance; the route is kept for
historical lookup and cross-referencing.
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/dashboards/gsd` | Dashboard page (stats + recent + unmaintained banner) |
| GET | `/dashboards/gsd/recent/partial?page=N` | HTMX partial — next 25 rows |
| GET | `/api/v1/gsd/{key}` | Raw aggregate JSON (`stats`, `recent`, `recent_all`) |
### Moksha dashboard (`/dashboards/moksha`, since v0.1.47)
Listed in the navbar under the **Enrichments & charts** group.
Surfaces moksha.dk
self-issued advisories (CVE JSON 5.1, `MOKSHA-YYYY-NNNN`,
GCVE-cross-referenced). The Moksha feeder downloads
`https://vulnerability.circl.lu/dumps/moksha.ndjson` over
TLS 1.3, **35 minutes after start then every 24 h**, and falls
back to the bundled `dumps_archive/moksha.7z` (SHA3-512-verified,
decompressed in-process) when the network is unavailable. Every
CVE a Moksha advisory references is cross-linked so the advisory
surfaces in those CVEs' aggregated JSON + enrichment cards.
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/dashboards/moksha` | Dashboard page (Last 25 + drill-down) |
| GET | `/dashboards/moksha/recent/partial?page=N` | HTMX partial — next 25 rows |
| GET | `/dashboards/moksha/<advisory_id>` | Per-advisory drill-down |
### CVE Forecast dashboard (`/cve-forecast`, since v1.2.56)
Listed under the **Statistics** navbar dropdown. Mirrors
[CVEForecast.org](https://cveforecast.org)'s daily
model-prediction JSON (per-CNA breakdown), refreshed hourly.
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/cve-forecast` | Dashboard page |
| GET | `/api/v1/cve-forecast/{key}` | Raw pre-aggregated forecast JSON |
### cve.icu Analytics dashboard (`/cve-icu`, since v1.2.56)
Listed under the **Statistics** navbar dropdown. Mirrors all 46 of
[cve.icu](https://cve.icu)'s per-document analytics JSON files
(growth, scoring, CWE, CPE, calendar, per-year and summary),
refreshed every 6 hours; each document is stored independently so
one upstream fetch failure doesn't block the others.
| Method | Endpoint | Description |
| -------- | ---------- | ------------- |
| GET | `/cve-icu` | Dashboard page |
| GET | `/api/v1/cve-icu/{key}` | Raw per-document analytics JSON |
## Architecture
```text
vulnerability-lookup-rs/
crates/
vl-core/ # Storage (redb), config, search,
# GCVE utils, notification dispatcher
vl-models/ # SQLite models (rusqlite) -- users,
# sightings, KEV, notifications
vl-feeders/ # 135 feeder implementations,
# native CSAF downloader
vl-web/ # Hyper + QUIC web server,
# annotation API + UI
vl-cli/ # Management CLI
```
**Tech stack**: hyper, redb, rusqlite, askama,
hashbrown, rustls, quinn (QUIC), tokio, reqwest.
## Code Health (rust-doctor)
Current workspace score — refreshed on every release tag and
checked into `crates/*/.rust-doctor-cache.json` so regressions
are visible in PR review:
**Score: 99 / 100 (Great)**
290 files scanned · 0 errors · ~20 warnings · 42 info · ~4 min
| Category | Score |
| ---------- | ------- |
| Security | **100** |
| Reliability | **99** |
| Maintainability | **99** |
| Performance | **99** |
| Dependencies | **99** |
Run locally:
```bash
cd vulnerability-lookup-rs
rust-doctor .
```
Policy (see `CLAUDE.md` quality loop, gate 5c): fail CI if
the score regresses by more than 5 points versus the last
committed baseline. Apply the canonical **Clippy auto-fix
sweep** (two lint-group rounds documented in `CLAUDE.md`
"Clippy auto-fix") before opening a PR if your changes push
maintainability below 70.
### Security posture (Security 100)
The full quality gate is `scripts/quality_gates.sh` (32 gates
as of v0.1.44; six were added today: `cargo_fa`,
`cargo_propagate_features`, `cargo_reedme`, `cargo_describe`,
`cargo_auditable` in `--fast`, plus `cargo_mend` in `--full`
only — `ALL_GATES` 92 → 98). Counts below are accurate as of
v0.1.37; CI re-runs the full sweep on every PR.
#### Static + supply-chain (every PR)
- Pure Rust, no OpenSSL (`rustls` only).
- TLS 1.3 mandatory on every build (the plaintext-HTTP
branch that used to sit behind `#[cfg(not(feature =
"quic"))]` was removed in v0.1.30).
- No external process spawning (pure-Rust CSAF downloader,
no Go binary).
- `cargo fmt --all --check`, `cargo clippy -- -D warnings`,
`cargo audit` (deny warnings + unsound + unmaintained +
yanked), `cargo deny check`, `cargo machete`, `cargo
geiger`, `cargo vet check`, `cargo semver-checks
check-release --workspace`, `rust-doctor`, `cargo
llvm-cov` — all green on `main`.
#### Tests we run
| Surface | Count | Source |
| --- | ---: | --- |
| Rust integration test files | 19 | `crates/*/tests/test_*.rs` |
| Rust unit + doc tests | 700+ | `cargo test --workspace --all-features` (+ `--doc`) |
| Kani model-checker proofs | 18 | `#[kani::proof]` annotations |
| `cargo-fuzz` targets | 62 | `crates/*/fuzz/fuzz_targets/*.rs` |
| Bruno API contract tests | 226 | `test/bruno/collections/<31 dirs>/*.bru` |
| Schemathesis OpenAPI/HATEOAS sweep | 1 runner | `tests/scripts/test_schemathesis.sh` (8180 + 8181, `-w 8`, 2 h ceiling, all formats + SARIF 2.1) |
#### DAST + live-server gates (skipped under `--fast`)
| Tool | Purpose | Output |
| --- | --- | --- |
| `nuclei` | DAST templates (ProjectDiscovery) | `documentation/vulnerabilities/nuclei/<ISO>/` |
| `lonkero` | DAST AI-driven (Ollama qwen3.6:35b) + max-coverage payload sweep | `documentation/vulnerabilities/lonkero/lonkero_{cli,lib}_<ISO>/` |
| `ffuf` | Content discovery (SecLists wordlist, all native formats + SARIF) | `documentation/vulnerabilities/ffuf/ffuf_cli_<ISO>/` |
| `feroxbuster` | Recursive content discovery (parallel 8, depth 10, all formats + SARIF) | `documentation/vulnerabilities/feroxbuster/feroxbuster_cli_<ISO>_<port>.<format>` |
| `sqlmap` | SQL-injection sweep | `documentation/sqlmap/<ISO>/` |
| `testssl.sh` | TLS 1.3 conformance | `documentation/tls/testssl/<ISO>/` |
| `OWASP ZAP` | DAST baseline + active | `target/zap/` |
| `oha` | Load test (HTTP/2 over TLS 1.3) | `documentation/loadtest/<ISO>/` |
| `schemathesis` | OpenAPI/HATEOAS contract testing | `documentation/api/schemathesis/<ISO>_<port>.<format>` |
#### Provenance / attestation
- 32 published CSAF 2.1 advisories under `csaf/YYYY/NNN/`
with `.sha-256`, `.sha-512`, and `.sha3-512` sidecars,
gocsaf-validated.
- Every release binary is built with `cargo-auditable`;
the embedded `.dep-v0` SBOM is scanned by Trivy + syft +
osv-scanner per release artefact.
- 0 SQL-injection findings (sqlmap full sweep + manual
Bruno payloads + per-handler Rust unit tests).
- 0 crashes across 62 `cargo-fuzz` targets at the
PR-default 60 s budget.
## Benchmarks
The repository ships two load-test profiles; both write
their reports to `documentation/loadtest/<ISO>/` so
release-to-release regressions are auditable.
**`oha` (HTTP/2 + HTTP/1.1 over TLS 1.3, default):**
```bash
./test/loadtest/run.sh https://127.0.0.1:8080
```
Env knobs: `OHA_REQUESTS` (default 500), `OHA_CONCURRENCY`
(default 20), `OHA_MAX_ERROR_RATE` (default 0.01 — hard
fail threshold). `oha` has no HTTP/3 support; the QUIC
listener on UDP 8081 is measured separately via
`neqo-client` or a custom h2load build — see
`documentation/Administrator_Guide.md` "Load testing" for
the toolchain.
**Latest headline numbers** (single-node macOS laptop,
release binary, self-signed dev cert, 500 requests × 20
concurrent):
| Endpoint | req/s | p50 | p95 | p99 |
| --- | ---: | ---: | ---: | ---: |
| `GET /api/v1/system/health` | 4 800 | 3 ms | 7 ms | 12 ms |
| `GET /api/v1/vulnerabilities/recent?limit=25` | 3 100 | 6 ms | 14 ms | 24 ms |
| `GET /dashboards/debian` | 2 400 | 8 ms | 19 ms | 31 ms |
| `GET /api/v1/debian/stats` | 4 200 | 4 ms | 9 ms | 16 ms |
Microbenchmarks for the hot-path parsers (CSAF,
`parse_sha512_sidecar`, `aggregate` in the Debian feeder,
`index_key`) live in `crates/*/benches/` and are driven by
`cargo criterion`. Baselines are checked into
`benches/baselines/` and compared automatically on every
release tag.
## Deployment with Ansible
An Ansible role is provided for automated
deployment on Debian 13 (x86_64 and arm64):
```bash
# Location
tools/ansible/roles/nvulnlookup/
# Usage in playbook
- hosts: vuln_servers
roles:
- role: nvulnlookup
nvulnlookup_version: "0.1.31"
nvulnlookup_listen_port: 8080
nvulnlookup_quic_port: 8081
```
The role handles:
- System user/group creation (`nvulnlookup`)
- Binary download with SHA-256 verification
- Hardened systemd service (NoNewPrivileges,
ProtectSystem=strict, syscall filters)
- UFW firewall rules (IPv4 + IPv6)
- AppArmor profile (complain/enforce mode)
- SELinux policy (permissive/enforcing mode)
- fapolicyd trust rules
- Falco runtime detection rules
- AIDE file integrity monitoring
- auditd rules (MITRE ATT&CK mapped)
- Logrotate configuration
```bash
# Run with Ansible
ansible-playbook -i inventory site.yml
# Molecule test (Podman)
cd tools/ansible/roles/nvulnlookup
molecule test
```
See
[Administrator Guide](documentation/Administrator_Guide.md)
for full deployment instructions.
## Documentation
- [Documentation Index](documentation/README.md)
- [User Guide](documentation/User_Guide.md)
- [Administrator Guide](documentation/Administrator_Guide.md)
- [Developer Guide](documentation/Developer_Guide.md)
- [API Reference](documentation/API_Reference.md)
- [Data Source Reference](documentation/Reference.md)
- [Troubleshooting](documentation/Troubleshooting_Guide.md)
- [Database Schema](documentation/db_schema.md)
- [Test Sequences](documentation/Test_Sequences.md)
- [How-To Guides](documentation/how-to/README.md)
- [Tutorials](documentation/tutorials/README.md)
- [Changelog](CHANGELOG.md)
## License
AGPL-3.0-or-later
Copyright (c) 2026 Pierre Gronau, ndaal in Cologne
## Links
- [Source Code](https://gitlab.com/vPierre/ndaal_public_nvulnlookup)
- [ndaal](https://ndaal.de)
- [Inspired by Vulnerability-Lookup (CIRCL)](https://www.vulnerability-lookup.org/)