README

<!-- 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/)