{"$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.42 corrects a long-standing CSAF Trusted Provider download bug in `crates/vl-feeders/src/csaf_downloader.rs`.  Releases v0.1.0 through v0.1.41 parsed `changes.csv` and `index.txt` line-by-line with `split_once(',')` and `line.to_owned()` and never stripped RFC 4180 surrounding double-quotes from CSV fields.  CSAF providers that quote every field in their distribution index — Red Hat is the canonical case (`https://security.access.redhat.com/data/csaf/v2/advisories/changes.csv` ships every path as `\"YYYY/rhsa-YYYY_NNNN.json\"`) — therefore had their advisory paths handed to `join_url` with the literal `\"` characters intact.  The URL builder then percent-encoded the quotes as `%22`, producing dead URLs of the form `https://security.access.redhat.com/data/csaf/v2/advisories/%222026/rhsa-2026_13868.json%22`.  Every advisory request from a quoted-CSV provider returned HTTP 404, and the cycle reissued the same dead URLs every six hours.\n\nNet effect on a v0.1.0..v0.1.41 production deployment: zero Red Hat CSAF advisories ever imported (and the same was almost certainly true for any other provider that conforms to RFC 4180 quoting in its index).  The 404 storm also produced runtime starvation: each cycle generated thousands of failed HTTP requests on the same tokio runtime that serves the web UI, so under load the operator-facing endpoints (`/dashboards/*`, `/api/v1/*`) timed out for several minutes per cycle.\n\nv0.1.42 adds a tiny `strip_csv_quotes(s)` helper that strips a leading + trailing `\"` pair (RFC 4180 §2.5) and applies it to every CSV field consumed by both parsers, plus three regression-pin tests in `csaf_downloader::tests`."},{"category":"description","text":"## Defect — RFC 4180 quoted CSV fields not unwrapped\n\nThe v0.1.0..v0.1.41 parsers in `crates/vl-feeders/src/csaf_downloader.rs` looked like this (abridged):\n\n  // BEFORE (v0.1.41)\n  pub fn parse_changes_csv(content: &str, since: Option<&DateTime<Utc>>)\n      -> Vec<AdvisoryRef>\n  {\n      content.lines().filter_map(|line| {\n          let line = line.trim();\n          if line.is_empty() || line.starts_with('#') { return None; }\n          let (path, ts_str) = line.split_once(',')?;\n          let path = path.trim().to_owned();   // <-- raw, no quote strip\n          if path.is_empty() { return None; }\n          let updated = parse_rfc3339(ts_str.trim());\n          // ... since-filter elided ...\n          Some(AdvisoryRef { path, updated })\n      }).collect()\n  }\n\n  pub fn parse_index_txt(content: &str) -> Vec<AdvisoryRef> {\n      content.lines().filter_map(|line| {\n          let line = line.trim();\n          if line.is_empty() || line.starts_with('#') { return None; }\n          Some(AdvisoryRef {\n              path: line.to_owned(),           // <-- raw, no quote strip\n              updated: None,\n          })\n      }).collect()\n  }\n\nRed Hat's CSAF Trusted Provider distribution wraps every field per RFC 4180:\n\n  \"2026/rhsa-2026_13868.json\",\"2026-05-08T00:00:00Z\"\n  \"2026/rhsa-2026_13854.json\",\"2026-05-08T00:00:00Z\"\n  ...\n\nThe parser stored `\"2026/rhsa-2026_13868.json\"` (length 28, quotes included) in `AdvisoryRef::path`.  `join_url(base, &adv_ref.path)` then handed it to the `url` crate which percent-encoded the literal `\"` as `%22`, yielding:\n\n  https://security.access.redhat.com/data/csaf/v2/advisories/%222026/rhsa-2026_13868.json%22\n\nNo Red Hat advisory exists at that URL (the path component should be `2026/rhsa-2026_13868.json` with no quotes), so every request returned HTTP 404.  Operator log:\n\n  WARN vl_feeders::csaf_downloader: Failed to download CSAF advisory error=HTTP request error: advisory HTTP 404 Not Found for https://security.access.redhat.com/data/csaf/v2/advisories/%222026/rhsa-2026_13868.json%22\n\nMultiplied across every advisory in the index (thousands of entries on Red Hat's six-hour cycle), the daemon emitted a flood of 404s every cycle and never imported a single Red Hat advisory.\n\n## Fix landed in v0.1.42\n\n  * NEW HELPER `strip_csv_quotes(s: &str) -> &str` in\n    `crates/vl-feeders/src/csaf_downloader.rs` — strips a leading\n    AND trailing `\\\"` pair when both are present, returns the input\n    unchanged otherwise.  Inner double-quotes (RFC 4180 escape\n    `\\\"\\\"`) are left untouched because no observed CSAF provider\n    ships them; if a future provider does, the strip will need to\n    be replaced with a real CSV reader (`csv` crate).\n\n  * `parse_changes_csv` now strips the path AND the timestamp\n    field via `strip_csv_quotes(...)` before further processing.\n\n  * `parse_index_txt` now strips the line content via\n    `strip_csv_quotes(...)` before assigning to\n    `AdvisoryRef::path`.\n\n  * Net effect: a Red Hat advisory ID `\\\"2026/rhsa-2026_13868.json\\\"`\n    in the upstream CSV is now stored as the bare string\n    `2026/rhsa-2026_13868.json`.  `join_url` builds the canonical\n    URL `https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_13868.json`\n    which returns HTTP 200 with the advisory JSON body.\n\n## Test coverage\n\n  * `cargo test -p vl-feeders --lib csaf_downloader` — 40 / 40\n    pass, including three new regression-pin tests:\n      test_parse_changes_csv_strips_rfc4180_quotes\n      test_parse_index_txt_strips_rfc4180_quotes\n      test_strip_csv_quotes_preserves_unquoted\n\n  * `test_strip_csv_quotes_preserves_unquoted` is the\n    counterpart guard: every CSAF provider that ships UNQUOTED\n    CSV / index.txt (the majority — CERT-Bund, Cisco, Siemens,\n    Schneider, ndaal itself) must NOT have its paths mangled by\n    the strip.  The test asserts that\n    `strip_csv_quotes(\\\"foo/bar.json\\\")` returns the input unchanged.\n\n  * The seven pre-existing `parse_changes_csv` / `parse_index_txt`\n    fixture tests still pass unchanged — the fix only adds a strip\n    step, it does not change behaviour for any input shape that\n    was already correct."},{"category":"details","text":"## Operational impact assessment\n\n  * Red Hat CSAF advisories — IMPACTED.\n    On v0.1.0..v0.1.41 the operator's `/dashboards/redhat`\n    recent-25 table showed only the handful of Red Hat\n    advisories that arrived through other channels (e.g.\n    cvelistv5 enrichment).  The dedicated Red Hat CSAF feeder\n    contributed zero rows.  After upgrading to v0.1.42 the next\n    feeder cycle (six hours) will populate the Red Hat dashboard\n    with thousands of advisories from `changes.csv`.\n\n  * Other quoted-CSV CSAF providers — POTENTIALLY IMPACTED.\n    Red Hat is the only provider where the bug was confirmed in\n    field telemetry, but any Trusted Provider that follows the\n    RFC 4180 quoting convention in its `changes.csv` /\n    `index.txt` was equally affected.  The list of providers in\n    `crates/vl-feeders/src/feeders/` that route through\n    `csaf_downloader` is ~80 entries; each will need a one-time\n    re-import after upgrading so any previously-skipped\n    advisories land.\n\n  * Daemon HTTP serving — IMPACTED under load.\n    Each broken cycle generated thousands of failed HTTP\n    requests on the tokio runtime.  Operators reporting\n    \\\"`/dashboards/...` not responding\\\" or \\\"app no reacting\\\"\n    on a v0.1.0..v0.1.41 deployment were almost certainly\n    seeing this 404 storm + concurrent CWE / Debian / NVD\n    parses contend for the runtime.  v0.1.42 stops the storm\n    at its source: well-formed URLs return HTTP 200, the\n    feeder completes its cycle in normal time, and the\n    runtime stays available to serve operator traffic.\n\n  * Database file format on disk — UNCHANGED.\n    No redb table changed.  The `AdvisoryRef::path` field\n    still uses the same `String` type; only its content\n    arrives without surrounding quotes now.\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\n  * Storage size — INCREASED post-upgrade.\n    The first post-fix cycle imports every Red Hat advisory\n    that was previously dropped on the floor (estimated 30 000\n    advisories at ~50 KB each ≈ 1.5 GB of new redb data).  Plus\n    similar amounts for any other quoted-CSV provider in the\n    catalogue.\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 HTTP\n  404s went outbound from the daemon to upstream providers;\n  no untrusted input was consumed unsafely.\n\n  However, this WAS a critical correctness bug with two\n  operationally significant downstream effects: (1) every\n  Red Hat advisory was silently missing from the\n  vulnerability database for the lifetime of the v0.1.x\n  series, so analysts triaging Red Hat product CVEs against\n  the local mirror got incomplete answers; and (2) the 404\n  storm produced enough runtime contention to make the\n  operator-facing web UI intermittently unresponsive under\n  feeder load.\n\nThis advisory is filed as `csaf_informational_advisory` per\nOASIS CSAF 2.1 to record a data-completeness and runtime-\nperformance fix in the v0.1.42 release cycle.  No CVE is\nassigned because the defect did not introduce an exploitable\nvulnerability in the product itself.","title":"Operational impact"},{"category":"general","text":"## Recommended action for downstream consumers\n\nUPGRADE: 0.1.41 → 0.1.42.  Either:\n\n  * Pull the v0.1.42 release tarball from\n    `release/v0.1.42/` (per-triple) once published.\n  * Update the Ansible role pin from 0.1.41 → 0.1.42 and\n    re-run `ansible-playbook -i inventory site.yml -e\n    nvulnlookup_version=0.1.42`.\n  * Or `cargo install --path vulnerability-lookup-rs/crates/vl-web`\n    from the v0.1.42 git tag.\n\nNo manual intervention is required after upgrading: the next\nfeeder cycle (six hours after restart) will populate every\npreviously-missed Red Hat advisory into the\n`VULNERABILITIES` + `SOURCE_INDEX` tables.  The redb\nstorage layer is unchanged so no on-disk migration is\nrequired.\n\n## Verification\n\n  # 1. Confirm the fix is in the source tree:\n  grep -n 'strip_csv_quotes' \\\n      vulnerability-lookup-rs/crates/vl-feeders/src/csaf_downloader.rs\n  # Expected: at least four matches (the fn definition + three\n  # call sites + one comment cross-reference).\n\n  # 2. Confirm the regression-pin tests pass:\n  cd vulnerability-lookup-rs && cargo test -p vl-feeders --lib \\\n      csaf_downloader::tests::test_parse_changes_csv_strips_rfc4180_quotes \\\n      csaf_downloader::tests::test_parse_index_txt_strips_rfc4180_quotes \\\n      csaf_downloader::tests::test_strip_csv_quotes_preserves_unquoted\n\n  # 3. On a running v0.1.42 server, watch the next Red Hat\n  # cycle land advisories instead of 404s:\n  tail -f /tmp/vl-web-0.1.42.log | grep -E 'Red Hat|csaf_redhat|csaf_downloader'\n  # Expected: a stream of `INFO ... advisory downloaded` lines,\n  # NOT `WARN ... advisory HTTP 404 Not Found ... %22 ... %22`.\n\n  # 4. After the cycle, count Red Hat advisories in the local\n  # store (should grow from ~1 to thousands):\n  curl -sk https://localhost:8080/api/v1/system/info | \\\n      jq '.storage.db_sizes.csaf_redhat'\n\n## Rollback\n\nNot recommended.  v0.1.41 had the data-completeness defect\ndocumented above.  If you must roll back for an unrelated\nreason, restore the v0.1.41 binary; the next Red Hat cycle\nwill re-revert to 404s and the redb table will not gain new\nentries.  No on-disk schema migration is required to roll\nback.  v0.1.42 introduced no new redb tables.","title":"Recommended action"},{"category":"other","text":"Disposition: informational — RFC 4180 CSV-field unwrap fix in `crates/vl-feeders/src/csaf_downloader.rs`.  Fixes a v0.1.0..v0.1.41 latent bug that prevented Red Hat (and any quoted-CSV CSAF provider) advisories from importing.  No exploitable vulnerability in the product itself.  Status final.  Followed up by the v0.1.42 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/040/ndaal-sa-2026-040.json"},{"category":"external","summary":"Companion v0.1.41 vendor-dashboard regression advisory ndaal-sa-2026-039","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/csaf/2026/039/ndaal-sa-2026-039.json"},{"category":"external","summary":"RFC 4180 — Common Format and MIME Type for Comma-Separated Values (CSV) Files (§2.5 quoting)","url":"https://www.rfc-editor.org/rfc/rfc4180"},{"category":"external","summary":"OASIS CSAF 2.1 specification — Trusted Provider distribution requirements","url":"https://docs.oasis-open.org/csaf/csaf/v2.1/cs01/csaf-v2.1-cs01.html#7211-distribution-with-changescsv"},{"category":"external","summary":"Red Hat CSAF Trusted Provider distribution root","url":"https://security.access.redhat.com/data/csaf/v2/"},{"category":"external","summary":"Red Hat CSAF changes.csv (the file that exposed the bug)","url":"https://security.access.redhat.com/data/csaf/v2/advisories/changes.csv"},{"category":"external","summary":"Rust `url` crate — RFC 3986 percent-encoding behaviour","url":"https://docs.rs/url/"},{"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.42 — CSAF Trusted Provider downloader (`crates/vl-feeders/src/csaf_downloader.rs`) parsed RFC 4180 quoted CSV fields verbatim from v0.1.0 through v0.1.41.  Red Hat (the canonical quoted-CSV provider) ships every path in `changes.csv` as `\"YYYY/rhsa-YYYY_NNNN.json\"`; the parser stored those quotes inside `AdvisoryRef::path` and `join_url` percent-encoded each `\"` as `%22`, yielding dead URLs of the form `…/advisories/%22YYYY/rhsa-YYYY_NNNN.json%22` that always returned HTTP 404.  Net effect: zero Red Hat advisories ever imported on v0.1.0..v0.1.41 and the 404 storm contributed to operator-facing HTTP timeouts under feeder load.  v0.1.42 introduces `strip_csv_quotes(s: &str) -> &str` and applies it to every CSV field consumed by `parse_changes_csv` and `parse_index_txt`; three regression-pin tests in `csaf_downloader::tests` lock the fix.","tracking":{"current_release_date":"2026-05-08T01:50:00.000Z","generator":{"engine":{"name":"ndaal CSAF Generator","version":"1.0.0"}},"id":"ndaal-sa-2026-040","initial_release_date":"2026-05-08T01:50:00.000Z","revision_history":[{"date":"2026-05-08T01:50:00.000Z","number":"1.0.0","summary":"Initial informational advisory documenting the v0.1.42 fix for the long-standing RFC 4180 CSV-quote-strip defect in `crates/vl-feeders/src/csaf_downloader.rs::{parse_changes_csv, parse_index_txt}`.  Defect history: present in v0.1.0 through v0.1.41.  Discovery context: a v0.1.41 production restart on 2026-05-08 surfaced an HTTP 404 storm in the daemon log against `https://security.access.redhat.com/data/csaf/v2/advisories/%22YYYY/rhsa-YYYY_NNNN.json%22` URLs.  The `%22` is the percent-encoded form of the RFC 4180 surrounding double-quote that Red Hat ships in every field of `changes.csv` and that our parser was preserving in `AdvisoryRef::path`.  Fix: a new `strip_csv_quotes(s: &str) -> &str` helper, applied to the path AND the timestamp field of `parse_changes_csv` and to the line content of `parse_index_txt`.  Three regression-pin unit tests added: `test_parse_changes_csv_strips_rfc4180_quotes`, `test_parse_index_txt_strips_rfc4180_quotes`, `test_strip_csv_quotes_preserves_unquoted` (the third guards against future regression that would mangle unquoted CSAF providers).  All 40 csaf_downloader tests pass.  Net effect: every Red Hat advisory (and any other quoted-CSV CSAF Trusted Provider) imports cleanly on v0.1.42; HTTP 404 storm and associated runtime starvation eliminated.  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.0|<=0.1.41","product":{"name":"nvulnlookup 0.1.0 through 0.1.41 — `crates/vl-feeders/src/csaf_downloader.rs::{parse_changes_csv, parse_index_txt}` does not strip RFC 4180 surrounding double-quotes from CSV fields; quoted-CSV CSAF Trusted Providers (Red Hat being the canonical case) have every advisory URL percent-encoded with stray `%22` markers and return HTTP 404","product_id":"CSAFPID-0001"}},{"category":"product_version","name":"0.1.42","product":{"name":"nvulnlookup 0.1.42 — `parse_changes_csv` and `parse_index_txt` route every CSV field through a new `strip_csv_quotes(s: &str) -> &str` helper that strips a leading + trailing `\"` pair (RFC 4180 §2.5); three regression-pin tests in `csaf_downloader::tests` lock the fix and protect against future regression that would mangle unquoted CSAF providers","product_id":"CSAFPID-0002"}}]}]}]}}