Summary
Some GAD-sourced rows in cve_range carry a malformed bound — versionStartExcluding = 0) — with every other bound empty. By the scanner's own range logic such a row matches every version of the product, so the CVE is reported against releases it cannot affect.
I hit this on a real Jenkins delivery and it produced two false positives at HIGH/CRITICAL.
The rows
From the local cve.db after a normal --update:
cve_number vendor product version vStartIncl vStartExcl vEndIncl vEndExcl data_source
CVE-2023-36478 unknown jetty-http * '' '0)' '' '' GAD
CVE-2022-31692 unknown spring-security-core * '' '0)' '' '' GAD
Both bounds that would limit the range are empty, and the one that is set is not a version.
Why such a row matches everything
Replaying CVEScanner.get_cves()'s range block (cve_bin_tool/cve_scanner.py, the passes_start / passes_end pair) against that row:
version 12.1.8 : start_excluding taken: 12.1.8 > Version('0)') -> True
no-end-range clause fired -> end always passes
passes_start=True passes_end=True -> REPORTED
version 0.0.1 : ... -> REPORTED
version 99.99 : ... -> REPORTED
Two things combine:
Version("0)") parses successfully — Version: 0) aka ['0'] — so the junk never surfaces as an error, and the comparison parsed_version > Version("0)") is true for anything above zero.
- With
versionEndIncluding and versionEndExcluding both unset, the "then there is no end range so it passes" branch makes passes_end unconditionally true. That branch is correct in itself (a CVE with no fix yet legitimately has no upper bound) — it just has nothing to push back on once the upper bound has been lost.
Real-world effect
Scanning an exploded jenkins.war 2.568.2 (251 nested archives):
| Reported |
Actual affected range |
|
jetty-http 12.1.8 — CVE-2023-36478, HIGH 7.5 |
http2-hpack / http3-qpack 9.3.0–9.4.52, 10.0.0–10.0.15, 11.0.0–11.0.15; the advisory states Jetty 12.x is unaffected, and jetty-http is not among the affected artifacts |
false positive |
spring-security-core 7.1.0 — CVE-2022-31692, CRITICAL 9.8 |
Spring Security 5.6.0–5.6.8 and 5.7.0–5.7.4 (fixed 5.6.9 / 5.7.5) |
false positive |
What I could not establish
I have not reproduced the GAD input that yields (0). Reading parse_range_string() in cve_bin_tool/data_sources/gad_source.py, two-sided tokens survive the round trip correctly — (0,1.2.3) becomes versionStartExcluding=0 plus versionEndExcluding=1.2.3, because the comma is turned into a space and each half hits its own branch. A single token (0) with no comma is what produces 0), since the if/elif chain takes the "(" in version branch and the ")" branch can then never run. Whether the advisory really contained (0), or something upstream collapsed a wider range into it, I can't say from the stored data alone.
So I'm reporting the observable defect rather than guessing at a fix. Two directions look reasonable and I'm happy to prepare either:
- reject a bound that is not a parseable version at ingest time, so a malformed range is dropped (or logged) instead of silently becoming match-everything;
- make
Version() refuse trailing junk, which would turn this class of problem into a loud failure everywhere rather than only here — though that is the more invasive of the two and would want its own discussion.
Separately, and explicitly unexplained
On the same report, bcprov-jdk18on 1.84 was flagged with CVE-2025-14813 and bcpg-jdk18on 1.84 with CVE-2026-3505 — in both cases 1.84 is the version that fixes the CVE. The stored rows look correct and should exclude it (versionStartIncluding=1.82.0, versionEndExcluding=1.84.0, and Version("1.84") == Version("1.84.0") is True), and replaying the same range logic on those rows gives passes_end=False. So those two do not come from the mechanism above, and I have not found the path that reported them. Mentioning it only in case it is familiar to someone — I'm not claiming a second bug on this evidence.
Environment: cve-bin-tool run through its SBOM path over a CycloneDX SBOM; DB refreshed the same day (NVD 396 849 entries / 2 592 171 range rows; the two rows above are data_source=GAD).
Summary
Some GAD-sourced rows in
cve_rangecarry a malformed bound —versionStartExcluding=0)— with every other bound empty. By the scanner's own range logic such a row matches every version of the product, so the CVE is reported against releases it cannot affect.I hit this on a real Jenkins delivery and it produced two false positives at HIGH/CRITICAL.
The rows
From the local
cve.dbafter a normal--update:Both bounds that would limit the range are empty, and the one that is set is not a version.
Why such a row matches everything
Replaying
CVEScanner.get_cves()'s range block (cve_bin_tool/cve_scanner.py, thepasses_start/passes_endpair) against that row:Two things combine:
Version("0)")parses successfully —Version: 0) aka ['0']— so the junk never surfaces as an error, and the comparisonparsed_version > Version("0)")is true for anything above zero.versionEndIncludingandversionEndExcludingboth unset, the "then there is no end range so it passes" branch makespasses_endunconditionally true. That branch is correct in itself (a CVE with no fix yet legitimately has no upper bound) — it just has nothing to push back on once the upper bound has been lost.Real-world effect
Scanning an exploded
jenkins.war2.568.2 (251 nested archives):jetty-http12.1.8 — CVE-2023-36478, HIGH 7.5http2-hpack/http3-qpack9.3.0–9.4.52, 10.0.0–10.0.15, 11.0.0–11.0.15; the advisory states Jetty 12.x is unaffected, andjetty-httpis not among the affected artifactsspring-security-core7.1.0 — CVE-2022-31692, CRITICAL 9.8What I could not establish
I have not reproduced the GAD input that yields
(0). Readingparse_range_string()incve_bin_tool/data_sources/gad_source.py, two-sided tokens survive the round trip correctly —(0,1.2.3)becomesversionStartExcluding=0plusversionEndExcluding=1.2.3, because the comma is turned into a space and each half hits its own branch. A single token(0)with no comma is what produces0), since theif/elifchain takes the"(" in versionbranch and the")"branch can then never run. Whether the advisory really contained(0), or something upstream collapsed a wider range into it, I can't say from the stored data alone.So I'm reporting the observable defect rather than guessing at a fix. Two directions look reasonable and I'm happy to prepare either:
Version()refuse trailing junk, which would turn this class of problem into a loud failure everywhere rather than only here — though that is the more invasive of the two and would want its own discussion.Separately, and explicitly unexplained
On the same report,
bcprov-jdk18on1.84 was flagged with CVE-2025-14813 andbcpg-jdk18on1.84 with CVE-2026-3505 — in both cases 1.84 is the version that fixes the CVE. The stored rows look correct and should exclude it (versionStartIncluding=1.82.0,versionEndExcluding=1.84.0, andVersion("1.84") == Version("1.84.0")isTrue), and replaying the same range logic on those rows givespasses_end=False. So those two do not come from the mechanism above, and I have not found the path that reported them. Mentioning it only in case it is familiar to someone — I'm not claiming a second bug on this evidence.Environment:
cve-bin-toolrun through its SBOM path over a CycloneDX SBOM; DB refreshed the same day (NVD 396 849 entries / 2 592 171 range rows; the two rows above aredata_source=GAD).