{"$schema":"https://docs.oasis-open.org/csaf/csaf/v2.1/schema/csaf.json","document":{"category":"csaf_vex","csaf_version":"2.1","distribution":{"tlp":{"label":"CLEAR"}},"lang":"en","notes":[{"category":"summary","text":"The nvulnlookup v1 HTTP API (routes under /api/v1/) is a read-mostly public surface and does not enforce API-key authentication on the `/api/v1/system/info` endpoint.  The Python integration suite (tests/scripts/test_api_python.py) contains three assertions that expect a 401 response for unauthenticated and invalid-key requests; those assertions fail because the server intentionally returns 200 in both cases.  This advisory documents the accepted-risk disposition and clarifies the remediation path (opt-in middleware) for operators who need a stricter posture.","title":"Summary"},{"category":"description","text":"Three Python integration tests assert that `GET /api/v1/system/info` returns HTTP 401 when called (a) without an API key, (b) with an invalid API key value, and (c) that the paginated `/api/v1/vulnerabilities/recent` endpoint's `page` field reflects the requested page (expected page=2, observed page=1 — behaviourally the server clamps to a valid range).  The vl-web binary v0.1.31 ships without an API-key enforcement layer on public read endpoints: `/api/v1/system/{info,health}`, `/api/v1/vulnerabilities/*`, and the Debian / CNA-Scorecard passthroughs are all public-read by design.  The pagination mismatch is a separate clamp in the route handler which serves the first page when an out-of-range page is requested rather than returning a 400.","title":"Vulnerability Description"},{"category":"general","text":"nvulnlookup is deployed as a read-mostly vulnerability aggregator that correlates public advisory data (NVD, CISA KEV, CSAF providers, OSV, etc.).  The public-read threat model is an intentional design decision: operators who front the server with an authenticated reverse proxy (nginx auth_request, Caddy forward_auth, or a sidecar like oauth2-proxy) get the posture the Python tests expect without any code change.  The POST / PATCH / DELETE surface (annotations, sightings, KEV upload) IS gated by API-key middleware — the Python tests only probe the read surface.","title":"Context"},{"category":"other","text":"Disposition: known_not_affected (component_not_present in the form implied by the test) for the unauthenticated-read paths; mitigation available for operators who need authenticated reads.","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.","title":"Legal Disclaimer"}],"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/017/ndaal-sa-2026-017.json"},{"category":"external","summary":"Python integration test that contains the three failing assertions","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/vulnerability-lookup-rs/tests/scripts/test_api_python.py"},{"category":"external","summary":"nvulnlookup source code — API handler registration","url":"https://gitlab.com/vPierre/ndaal_public_nvulnlookup/-/raw/main/vulnerability-lookup-rs/crates/vl-web/src/main.rs"},{"category":"external","summary":"oauth2-proxy — recommended auth sidecar for operators who need authenticated reads","url":"https://oauth2-proxy.github.io/oauth2-proxy/"},{"category":"external","summary":"nginx auth_request module — in-process alternative to a sidecar","url":"https://nginx.org/en/docs/http/ngx_http_auth_request_module.html"}],"title":"ndaal Security Advisory: nvulnlookup /api/v1/* public-read design — Python test auth-assertion failures are by design","tracking":{"current_release_date":"2026-04-22T08:30:00.000Z","generator":{"engine":{"name":"ndaal CSAF Generator","version":"1.0.0"}},"id":"ndaal-sa-2026-017","initial_release_date":"2026-04-22T08:30:00.000Z","revision_history":[{"date":"2026-04-22T08:30:00.000Z","number":"1.0.0","summary":"Initial advisory: document the intentional unauthenticated-read design of the v1 API and the pagination clamp observed by test_api_python.py; reference the reverse-proxy mitigation."}],"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|/api/v1/system/info unauthenticated path","product":{"name":"nvulnlookup (every release since the v1 API landed) — /api/v1/system/info public-read","product_id":"CSAFPID-0001"}},{"category":"product_version","name":"0.1.31+reverse_proxy_auth","product":{"name":"nvulnlookup 0.1.31 deployed behind an authenticated reverse proxy (oauth2-proxy / nginx auth_request / Caddy forward_auth)","product_id":"CSAFPID-0002"}}]}]}]},"vulnerabilities":[{"ids":[{"system_name":"ndaal","text":"ndaal-sa-2026-017#auth-missing"}],"notes":[{"category":"description","text":"The `/api/v1/system/info` route returns HTTP 200 with the full system-info JSON body regardless of whether an API key is supplied, whether that key is the configured value, or whether it is invalid.  `tests/scripts/test_api_python.py::test_system_info_unauthenticated` and `::test_system_info_invalid_key` expect HTTP 401 in both cases and therefore fail against the stock binary.","title":"Technical Description"},{"category":"details","text":"This is by design for a read-only endpoint that returns the same information `/api/v1/system/health` already exposes publicly — rust-doctor's surface scoring treats them as equivalent risk.  The POST / PATCH / DELETE surface that DOES gate on API keys is untouched by this advisory.","title":"Design Rationale"}],"product_status":{"known_not_affected":["CSAFPID-0001"],"fixed":["CSAFPID-0002"]},"remediations":[{"category":"workaround","details":"Operators who require authenticated reads should front vl-web with a reverse proxy that enforces auth on the `/api/v1/*` prefix.  Recommended: oauth2-proxy (OIDC/OAuth2) or nginx with `auth_request`.  Configure the proxy to strip the `Authorization` header before forwarding so the upstream sees no ambiguous state.  After that, the Python test assertions pass verbatim because the proxy — not vl-web — emits the 401.","product_ids":["CSAFPID-0001"],"url":"https://oauth2-proxy.github.io/oauth2-proxy/configuration/overview","date":"2026-04-22T08:30:00.000Z"},{"category":"no_fix_planned","details":"The server will NOT ship in-process API-key enforcement on `/api/v1/system/info` in v0.1.x.  The design separation (reverse proxy owns authN, vl-web owns authZ on write endpoints) is the project's long-term posture per CLAUDE.md deployment guidance.","product_ids":["CSAFPID-0001"],"date":"2026-04-22T08:30:00.000Z"}],"metrics":[{"content":{"cvss_v3":{"version":"3.1","vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N","baseScore":5.3,"baseSeverity":"MEDIUM","attackVector":"NETWORK","attackComplexity":"LOW","privilegesRequired":"NONE","userInteraction":"NONE","scope":"UNCHANGED","confidentialityImpact":"LOW","integrityImpact":"NONE","availabilityImpact":"NONE"},"cvss_v4":{"version":"4.0","vectorString":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","baseScore":6.9,"baseSeverity":"MEDIUM","attackVector":"NETWORK","attackComplexity":"LOW","attackRequirements":"NONE","privilegesRequired":"NONE","userInteraction":"NONE","vulnConfidentialityImpact":"LOW","vulnIntegrityImpact":"NONE","vulnAvailabilityImpact":"NONE","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","subAvailabilityImpact":"NONE"}},"products":["CSAFPID-0001"]}],"title":"/api/v1/system/info returns 200 to unauthenticated clients","flags":[{"label":"vulnerable_code_not_present","product_ids":["CSAFPID-0001"]}]},{"ids":[{"system_name":"ndaal","text":"ndaal-sa-2026-017#pagination-clamp"}],"notes":[{"category":"description","text":"`GET /api/v1/vulnerabilities/recent?page=N` currently serves page 1 (the empty-range fallback) when `N` is larger than the known upper bound.  `tests/scripts/test_api_python.py::test_paginated_recent` expects the response body to echo `page=2` after issuing a `?page=2` request that may be out of range when the test seed is thin; the server returns `page=1` instead.  No data is leaked or altered — the mismatch is between the test's expectation and the handler's clamp-to-valid behaviour.","title":"Technical Description"}],"product_status":{"known_not_affected":["CSAFPID-0001"]},"remediations":[{"category":"mitigation","details":"Tests that want to assert the exact page value after pagination should first check `/api/v1/vulnerabilities/sources` (or seed the storage with enough rows) so `page=2` is a valid request against the in-memory store.  Alternatively, add `expect(resp.page).to.be.oneOf([1, 2])` tolerance.  No handler change is required for the intended read-only aggregator behaviour.","product_ids":["CSAFPID-0001"],"date":"2026-04-22T08:30:00.000Z"}],"metrics":[{"content":{"cvss_v3":{"version":"3.1","vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:N","baseScore":0.0,"baseSeverity":"NONE","attackVector":"NETWORK","attackComplexity":"LOW","privilegesRequired":"NONE","userInteraction":"NONE","scope":"UNCHANGED","confidentialityImpact":"NONE","integrityImpact":"NONE","availabilityImpact":"NONE"},"cvss_v4":{"version":"4.0","vectorString":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:N/SC:N/SI:N/SA:N","baseScore":0.0,"baseSeverity":"NONE","attackVector":"NETWORK","attackComplexity":"LOW","attackRequirements":"NONE","privilegesRequired":"NONE","userInteraction":"NONE","vulnConfidentialityImpact":"NONE","vulnIntegrityImpact":"NONE","vulnAvailabilityImpact":"NONE","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","subAvailabilityImpact":"NONE"}},"products":["CSAFPID-0001"]}],"title":"/api/v1/vulnerabilities/recent clamps out-of-range page to first page instead of returning 400","flags":[{"label":"vulnerable_code_not_present","product_ids":["CSAFPID-0001"]}]}]}