{"$schema":"https://docs.oasis-open.org/csaf/csaf/v2.1/schema/csaf.json","document":{"category":"csaf_informational_advisory","csaf_version":"2.1","distribution":{"tlp":{"label":"CLEAR"}},"lang":"en","notes":[{"category":"summary","text":"nvulnlookup v0.1.41 corrects a column-rendering regression on the vendor-dashboard recent-25 tables for every dashboard whose feeder ships CVE 5.0 documents (vulnrichment, cvelistv5, ubuntu, rust). Releases v0.1.36 through v0.1.40 rendered the CVE-ID column correctly but left the Severity, Published, and Title columns blank because the helper functions in `crates/vl-web/src/routes/vendor_dashboard.rs` (`extract_title`, `extract_severity`, `extract_published`) only walked legacy CSAF `/document/...` and OSV `severity[]` paths.  The CVE 5.0 schema places the same fields under `/containers/cna/...` and `/cveMetadata/...` — paths the dashboard helpers had no knowledge of.  The per-vulnerability detail page at `/vulnerability/<id>` rendered correctly because its helpers in `routes/home.rs` walked the CVE 5.0 paths.  The defect was a write-through case of code duplication: an earlier release (v0.1.36 cycle) added CVE 5.0 awareness to `routes/home.rs` only, leaving `routes/vendor_dashboard.rs` behind.  v0.1.41 introduces a shared `crates/vl-web/src/routes/cve5.rs` module that both call sites delegate to, removing the divergence and restoring the recent-25 columns on every CVE-5.0-shaped dashboard."},{"category":"description","text":"## Defect — vendor dashboards lost three columns on CVE 5.0 sources\n\nFour of the twenty vendor dashboards under `/dashboards/<slug>` consume feeders that ship CVE 5.0 (cvelistv5 schema) documents:\n\n  * `/dashboards/vulnrichment` — CISA enrichment of cvelistv5 records\n  * `/dashboards/cvelistv5`    — MITRE CVE List v5 baseline\n  * `/dashboards/ubuntu`       — Ubuntu Security Notices, CVE-5.0-shaped\n  * `/dashboards/rust`         — RustSec advisories, CVE-5.0-shaped\n\nA CVE 5.0 document carries the title under `/containers/cna/title` (or `/containers/cna/descriptions[lang=en].value`), the severity under `/containers/cna/metrics[].cvssV{4_0,3_1,3_0,2_0}.baseSeverity` (with a `baseScore` fallback when the upstream omitted the bucketed string), and the publication timestamp under `/cveMetadata/datePublished`.\n\nThe legacy `extract_title`, `extract_severity`, `extract_published` helpers in `crates/vl-web/src/routes/vendor_dashboard.rs` (lines 644-693 in v0.1.40) only walked CSAF and OSV paths:\n\n  // BEFORE (v0.1.40)\n  fn extract_title(value: &serde_json::Value) -> String {\n      let raw = value\n          .pointer(\"/document/title\").and_then(|v| v.as_str())\n          .or_else(|| value.get(\"summary\").and_then(|v| v.as_str()))\n          .or_else(|| value.get(\"details\").and_then(|v| v.as_str()))\n          .or_else(|| value.get(\"title\").and_then(|v| v.as_str()))\n          .unwrap_or(\"\");\n      truncate_ellipsis(raw, MAX_LEN)\n  }\n\nNet effect on a v0.1.40 production deployment: each row in the \"Last 25 recent entries\" card on those four dashboards rendered as `[CVE-2025-31970] [empty] [empty] [empty]` — the operator could navigate to the detail page via the link, but the row did not communicate severity, publication date, or title at a glance.  The detail page itself rendered correctly because its helpers in `crates/vl-web/src/routes/home.rs` walked the CVE 5.0 paths via `/containers/cna/...`.\n\n## Fix landed in v0.1.41\n\n  * NEW MODULE `crates/vl-web/src/routes/cve5.rs` — exports the\n    canonical CVE 5.0 / NVD / CSAF / OSV / EUVD field extractors\n    as `pub(crate)` free functions.  Eight helpers:\n      extract_title, extract_updated, extract_severity,\n      extract_cvss, extract_description, extract_published,\n      format_date_string, score_to_severity\n\n  * MIGRATED `crates/vl-web/src/routes/home.rs` — the eight\n    inline helpers were deleted; the file now imports the same\n    names from `super::cve5`.  Behaviour is unchanged on the\n    detail page side.\n\n  * MIGRATED `crates/vl-web/src/routes/vendor_dashboard.rs` —\n    the three local extractors retain their CSAF / OSV first-pass\n    logic (so existing fixture-driven unit tests continue to\n    return the exact pre-fix strings, e.g. `\"High\"` not\n    `\"HIGH\"`) and now fall back to `cve5::extract_*` when no\n    CSAF / OSV path matches.  Net effect: CVE 5.0 documents that\n    previously yielded the empty string now render with the\n    CVE 5.0 title, severity, and published-date strings\n    extracted by the shared module.\n\n  * BUG FIX inside the lift: `cve5::extract_title` adds two\n    new fallbacks that the pre-v0.1.41 `home.rs::extract_title`\n    was also missing — `/containers/cna/title` (the canonical\n    CVE 5.0 location) and the first English entry under\n    `/containers/cna/descriptions[lang=en].value` (the most\n    common shape from CVE Numbering Authorities).  The detail\n    page therefore also benefits from this change: CVE\n    documents that carry only a description and no separate\n    `title` field now render the description as the heading\n    instead of falling through to the CVE ID.\n\n## Test coverage\n\n  * `cargo check -p vl-web` — clean.\n  * `cargo test -p vl-web --lib` — 210 / 210 pass, including\n    seven new tests guarding the regression and the lift:\n      cve5::tests::extract_title_handles_cve5_cna_title\n      cve5::tests::extract_title_falls_back_to_cna_description\n      cve5::tests::extract_title_falls_back_to_id_when_unknown\n      cve5::tests::extract_severity_reads_cna_cvssv4_basescore\n      cve5::tests::extract_severity_reads_cna_cvssv31_baseseverity\n      cve5::tests::extract_severity_reads_adp_ssvc_active_as_critical\n      cve5::tests::extract_published_reads_cve_metadata_date\n      vendor_dashboard::tests::test_extract_title_falls_back_to_cve5_cna_title\n      vendor_dashboard::tests::test_extract_severity_falls_back_to_cve5_basescore\n      vendor_dashboard::tests::test_extract_published_falls_back_to_cve5_metadata\n      vendor_dashboard::tests::test_extract_severity_unknown_yields_empty_string\n\n  * The four pre-existing CSAF + OSV unit tests in\n    `vendor_dashboard::tests` still pass unchanged (proves the\n    fallback design did not change observable behaviour for any\n    CSAF or OSV row): test_extract_title_csaf_pointer,\n    test_extract_title_osv_summary, test_extract_severity_csaf,\n    test_extract_severity_osv_array, test_extract_published_csaf."},{"category":"details","text":"## Operational impact assessment\n\n  * Production `vl-web` server runtime — IMPACTED.\n    On any v0.1.36..v0.1.40 deployment the operator's\n    `/dashboards/vulnrichment`, `/dashboards/cvelistv5`,\n    `/dashboards/ubuntu`, `/dashboards/rust` recent-25 tables\n    showed the CVE-ID column populated and the Severity,\n    Published, and Title columns blank.  After upgrading to\n    v0.1.41 those three columns populate from the CVE 5.0\n    payload that has been on disk all along — no feeder\n    re-run, no on-disk migration, no cache invalidation.\n\n  * Production `vl-cli` runtime — UNAFFECTED.\n    The CLI uses the per-vulnerability detail-page extractors\n    in `routes/home.rs`, which were already CVE-5.0-aware.\n    No behaviour change for any CLI command.\n\n  * Database file format on disk — UNCHANGED.\n    The redb tables and the JSON payload shape are identical.\n    The fix is purely on the read path; the storage layer was\n    already carrying the data the dashboard now displays.\n\n  * API surface — UNCHANGED.\n    The HATEOAS envelope and JSON shapes returned by\n    `/api/v1/vulnerabilities/...`, `/api/v1/dashboards/...`,\n    and `/api/v1/system/info` are byte-for-byte identical.\n    Operators who scripted around the dashboard API are\n    unaffected.\n\n  * The new `crates/vl-web/src/routes/cve5.rs` module is\n    `pub(crate)` only — it does not appear in any public\n    Rust API surface.  Downstream crates that re-export\n    `vl-web` types are unaffected.\n\n  * Operator deployments via the Ansible role\n    (`tools/ansible/roles/{nvulnlookup,nvulnlookuptesting}`) —\n    UNAFFECTED at the deploy layer.  The role copies the\n    binary and starts the systemd unit; no new role variables.\n\nSecurity classification: NOT a security vulnerability under the\n  CVSS framework.  The defect did not expose data, did not\n  allow privilege escalation, did not crash the daemon, and\n  did not create an unauthenticated attack surface.  The\n  vulnerability data was already on disk and already reachable\n  via the per-CVE detail page; the regression only suppressed\n  the dashboard's recent-25 summary columns.\n\n  However, this WAS an operationally relevant correctness\n  bug: an analyst triaging the recent-25 list on the four\n  CVE-5.0 dashboards had no at-a-glance signal of severity\n  or publication date, so a CRITICAL CVE landing in the table\n  was visually indistinguishable from a LOW or INFO entry.\n  v0.1.41 closes the analyst-experience gap and removes the\n  duplication that allowed the divergence to land in the\n  first place.\n\nThis advisory is filed as `csaf_informational_advisory` per\nOASIS CSAF 2.1 to record a UX correctness fix in the v0.1.41\nrelease cycle.  No CVE is assigned because the defect did not\nintroduce an exploitable vulnerability in the product itself.","title":"Operational impact"},{"category":"general","text":"## Recommended action for downstream consumers\n\nUPGRADE: 0.1.40 → 0.1.41.  Either:\n\n  * Pull the v0.1.41 release tarball from\n    `release/v0.1.41/` (per-triple) once published.\n  * Update the Ansible role pin from 0.1.40 → 0.1.41 (already\n    done in this commit) and re-run\n    `ansible-playbook -i inventory site.yml -e\n    nvulnlookup_version=0.1.41`.\n  * Or `cargo install --path vulnerability-lookup-rs/crates/vl-web`\n    from the v0.1.41 git tag.\n\nNo manual intervention is required after upgrading: the recent-25\ntables on the four affected dashboards re-render against the same\non-disk data and the columns populate immediately.  No feeder\nrestart, no cache invalidation, no on-disk migration.\n\n## Verification\n\n  # 1. Confirm the cve5 module is present in v0.1.41:\n  ls vulnerability-lookup-rs/crates/vl-web/src/routes/cve5.rs\n  # Expected: file exists, ~340 lines.\n\n  # 2. Confirm the dashboard helpers fall back to cve5:\n  grep -n 'cve5::' \\\n      vulnerability-lookup-rs/crates/vl-web/src/routes/vendor_dashboard.rs\n  # Expected: at least three matches, one per extractor.\n\n  # 3. Confirm the regression-pin tests pass:\n  cd vulnerability-lookup-rs && cargo test -p vl-web --lib \\\n      'routes::vendor_dashboard::tests::test_extract_title_falls_back_to_cve5_cna_title' \\\n      'routes::vendor_dashboard::tests::test_extract_severity_falls_back_to_cve5_basescore' \\\n      'routes::vendor_dashboard::tests::test_extract_published_falls_back_to_cve5_metadata'\n\n  # 4. On a running v0.1.41 server, confirm the recent-25\n  # table on each of the four dashboards renders Severity /\n  # Published / Title columns:\n  curl -sk https://localhost:8080/dashboards/vulnrichment \\\n      | grep -A2 'severity-badge' | head -10\n  # Expected: each row carries a non-empty `severity-badge`\n  # span text (CRITICAL, HIGH, MEDIUM, LOW, NONE, or UNKNOWN).\n\n  # 5. Same check via the API (HATEOAS payload):\n  curl -sk https://localhost:8080/api/v1/dashboards/vulnrichment/recent_partial \\\n      | jq '.data.rows[0] | {id, severity, published, title}'\n  # Expected: every field non-empty for any row whose payload\n  # ships a CVE 5.0 schema.\n\n## Rollback\n\nNot recommended.  v0.1.40 had the column-rendering regression\ndocumented above.  If you must roll back for an unrelated\nreason, restore the v0.1.40 binary; the recent-25 tables on\nthe four dashboards revert to the empty-columns state.  No\non-disk schema migration is required to roll back.  v0.1.41\nintroduced no new redb tables.","title":"Recommended action"},{"category":"other","text":"Disposition: informational — vendor-dashboard column-rendering correctness fix in `crates/vl-web/src/routes/vendor_dashboard.rs`, plus a small fix to the CVE-5.0 title extractor in the new shared `crates/vl-web/src/routes/cve5.rs` module.  No exploitable vulnerability in the product itself.  Status final.  Followed up by the v0.1.41 release cycle.","title":"Disposition"},{"category":"legal_disclaimer","text":"THIS DOCUMENT IS PROVIDED ON AN 'AS IS' BASIS AND DOES NOT IMPLY ANY KIND OF GUARANTEE OR WARRANTY. ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS INFORMATION INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS."}],"publisher":{"category":"vendor","contact_details":"security@ndaal.eu","issuing_authority":"ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG Security Team","name":"ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG","namespace":"https://ndaal.eu/csaf"},"references":[{"category":"self","summary":"This advisory in CSAF 2.1 format","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/csaf/2026/039/ndaal-sa-2026-039.json"},{"category":"external","summary":"Companion v0.1.40 EPSS/KEV feeder data-source advisory ndaal-sa-2026-038","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/csaf/2026/038/ndaal-sa-2026-038.json"},{"category":"external","summary":"MITRE CVE Record Format v5.x specification (the schema CVE List v5 / cvelistv5 ships)","url":"https://github.com/CVEProject/cve-schema"},{"category":"external","summary":"CISA vulnrichment program (the feeder that lands ADP-enriched CVE 5.0 records)","url":"https://github.com/cisagov/vulnrichment"},{"category":"external","summary":"FIRST.org CVSS v4.0 specification (the cvssV4_0 metric path the cve5 module reads)","url":"https://www.first.org/cvss/v4-0/"},{"category":"external","summary":"OASIS CSAF 2.1 specification (the schema this advisory itself ships)","url":"https://docs.oasis-open.org/csaf/csaf/v2.1/cs01/csaf-v2.1-cs01.html"},{"category":"external","summary":"nvulnlookup CHANGELOG (root)","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/CHANGELOG.md"},{"category":"external","summary":"nvulnlookup CHANGELOG (workspace mirror)","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/vulnerability-lookup-rs/CHANGELOG.md"}],"title":"ndaal Informational Advisory: v0.1.41 — vendor-dashboard recent-25 tables on CVE-5.0 sources (vulnrichment / cvelistv5 / ubuntu / rust) regressed to render empty Severity / Published / Title columns from v0.1.36 onwards; the helpers in `routes/vendor_dashboard.rs` only walked legacy CSAF / OSV paths and missed `/containers/cna/...` and `/cveMetadata/...`.  v0.1.41 ships a shared `routes/cve5.rs` module with the canonical CVE 5.0 / NVD / CSAF / OSV / EUVD extractors and rewires both `routes/home.rs` and `routes/vendor_dashboard.rs` to delegate to it; the dashboard helpers retain their CSAF / OSV first-pass logic and fall back to cve5 for CVE-5.0-shaped rows.  Bonus fix: the lifted `extract_title` now also reads `/containers/cna/title` and `/containers/cna/descriptions[lang=en].value`, two paths the pre-v0.1.41 `home.rs::extract_title` was also missing.","tracking":{"current_release_date":"2026-05-07T15:00:00.000Z","generator":{"engine":{"name":"ndaal CSAF Generator","version":"1.0.0"}},"id":"ndaal-sa-2026-039","initial_release_date":"2026-05-07T15:00:00.000Z","revision_history":[{"date":"2026-05-07T15:00:00.000Z","number":"1.0.0","summary":"Initial informational advisory documenting the v0.1.41 vendor-dashboard column-rendering correctness fix in `crates/vl-web/src/routes/vendor_dashboard.rs` plus the supporting refactor that lifted the eight CVE 5.0 / NVD / CSAF / OSV / EUVD field extractors from `routes/home.rs` into a new shared `routes/cve5.rs` module.  Bonus fix landed in the same lift: `extract_title` now reads `/containers/cna/title` and `/containers/cna/descriptions[lang=en].value`, two CVE 5.0 paths the pre-v0.1.41 `home.rs::extract_title` was also missing.  Test coverage: 210 / 210 unit tests in vl-web pass, including seven new tests in `routes::cve5::tests` and four new fallback-pin tests in `routes::vendor_dashboard::tests`.  All five pre-existing CSAF / OSV fixture tests still pass unchanged.  No on-disk migration, no API change, no feeder restart required.  Status: final."}],"status":"final","version":"1.0.0"}},"product_tree":{"branches":[{"category":"vendor","name":"ndaal Gesellschaft fuer Sicherheit in der Informationstechnik mbH & Co KG","branches":[{"category":"product_name","name":"nvulnlookup","branches":[{"category":"product_version_range","name":"vers:cargo/>=0.1.36|<=0.1.40","product":{"name":"nvulnlookup 0.1.36 through 0.1.40 — vendor-dashboard recent-25 helpers in `routes/vendor_dashboard.rs` only walk CSAF / OSV paths and leave Severity / Published / Title columns empty on `/dashboards/{vulnrichment,cvelistv5,ubuntu,rust}`","product_id":"CSAFPID-0001"}},{"category":"product_version","name":"0.1.41","product":{"name":"nvulnlookup 0.1.41 — vendor-dashboard helpers retain CSAF / OSV first-pass logic and fall back to a new shared `routes/cve5.rs` module for CVE-5.0 / NVD / EUVD records; recent-25 tables now render Severity / Published / Title columns on every dashboard regardless of upstream schema","product_id":"CSAFPID-0002"}}]}]}]}}