Raw JSON
{
"$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"
]
}
]
}
]
}