A record whose `version` equals its upper bound was treated the same way regardless of WHICH bound it is, and the two mean different things. version == lessThan is a zero-width, impossible range. Chrome emits it, NVD reads it as an open floor, and that is right: a Chrome CVE fixed in 151.0.7922.72 does affect 150.x. Unchanged. version == lessThanOrEqual is inclusive, so the entry names ONE release line. Node states CVE-2026-56846 as two entries — 24.18.0 and 22.23.1 — and its description says "affects Node.js 24.x and 22.x". Opening the floor made "up to 24.18.0" swallow everything older: a host on 22.23.2, already patched and covered by the OTHER entry, matched through the 24.x range, and so did 20.x, which is not affected at all. The major version is now kept as the floor, confining each entry to its own line. This also closes the false negative that surfaced it: Node 24.13.1 was reported unaffected because the entry was read as the single version 24.18.0 rather than the line it describes. Index key bumped to v19 — cached ranges carry the old floors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
"""version == bound: two meanings — run: python tests/test_inclusive_bound.py
|
|
|
|
A record whose `version` equals its upper bound reads differently depending on
|
|
which bound it is, and treating both the same produced false positives.
|
|
|
|
version == lessThan zero-width, impossible. Chrome emits it; NVD
|
|
reads it as an open floor, and rightly so — a
|
|
Chrome CVE fixed in 151.0.7922.72 does affect
|
|
150.x.
|
|
|
|
version == lessThanOrEqual inclusive, so the entry names ONE release line.
|
|
Node states CVE-2026-56846 as 24.18.0 and
|
|
22.23.1, and its description says "affects
|
|
Node.js 24.x and 22.x". Opening the floor made
|
|
"up to 24.18.0" swallow 22.23.2 — already
|
|
patched — and 20.x, never affected at all.
|
|
"""
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from app.services.cvelistv5_scan_service import _affected, _ranges_from_affected
|
|
|
|
|
|
def _hits(aff, version):
|
|
return any(_affected(version, s, lt, lte)
|
|
for s, lt, lte in _ranges_from_affected(aff))
|
|
|
|
|
|
def demo():
|
|
# CVE-2026-56846 verbatim.
|
|
node = {"vendor": "nodejs", "product": "node", "defaultStatus": "unaffected",
|
|
"versions": [
|
|
{"version": "24.18.0", "lessThanOrEqual": "24.18.0",
|
|
"versionType": "semver", "status": "affected"},
|
|
{"version": "22.23.1", "lessThanOrEqual": "22.23.1",
|
|
"versionType": "semver", "status": "affected"}]}
|
|
assert _ranges_from_affected(node) == [("24", None, "24.18.0"),
|
|
("22", None, "22.23.1")]
|
|
assert _hits(node, "24.13.1") # the tester's host — was a false negative
|
|
assert _hits(node, "24.18.0") # the bound itself, inclusive
|
|
assert not _hits(node, "24.18.1") # fixed
|
|
assert _hits(node, "22.20.0") # the other release line
|
|
assert not _hits(node, "22.23.2") # fixed there too — was a false positive
|
|
assert not _hits(node, "20.5.0") # never affected — was a false positive
|
|
|
|
# Chrome keeps its open floor: version == lessThan is a different case.
|
|
chrome = {"vendor": "Google", "product": "Chrome",
|
|
"versions": [{"version": "151.0.7922.72",
|
|
"lessThan": "151.0.7922.72",
|
|
"versionType": "custom", "status": "affected"}]}
|
|
assert _ranges_from_affected(chrome) == [(None, "151.0.7922.72", None)]
|
|
assert _hits(chrome, "150.0.7871.182") # older major still affected
|
|
assert _hits(chrome, "151.0.7922.71")
|
|
assert not _hits(chrome, "151.0.7922.72")
|
|
|
|
print("inclusive bound OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|