Files
vulncheck/tests/test_inclusive_bound.py
T
vulncheckandClaude Opus 5 3384a1e918 fix(scan): an inclusive bound names one release line, not everything below
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>
2026-08-05 13:12:40 +02:00

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()