Field case: Blender 5.0.0 (Intune) and 5.0.1 (Wazuh) got CVE-2026-60103 but none of CVE-2026-119718..119722, published 2026-10-30. The same CNA (VulnCheck) wrote the new records as vendor "Blender Foundation" / product "Blender", while CVE-2026-60103 says "blender" / "blender". Only the latter was paired, so the five records passed the byte prefilter and were dropped at pairing. A fresher nightly index would not have changed that. NVD has CVE-2026-119722 "Awaiting Analysis" with no configuration, so cvelistV5 is still the only structured source. Each record states two lines, "0" .. lessThan "4.5.15" and "5.0.0" .. lessThan "5.2.3"; they are matched per line, so 5.0.x is shown the 5.2.3 fix and the 4.5 LTS line the 4.5.15 one. All five IDs go through the real index build in the test. Index key bumped to v56.
152 lines
6.7 KiB
Python
152 lines
6.7 KiB
Python
"""Blender — run: python tests/test_blender.py
|
|
|
|
Field case: Intune lists "Blender" 5.0.0 and Wazuh "Blender" 5.0.1 (Blender
|
|
Foundation); neither got CVE-2026-60103 (3.0.0 through 5.1.2). Blender had no
|
|
row on any path.
|
|
|
|
NVD has the record "Deferred" with no configuration, and its blender:blender
|
|
rows stop at 2022, so cvelistV5 is the only structured source:
|
|
blender / blender, "3.0.0" .. lessThanOrEqual "5.1.2", plus an "unaffected"
|
|
entry that is a git commit (not a version, ignored).
|
|
|
|
Only the vendor's own spelling is paired. The 2022 records name the vendor
|
|
"n/a" and write prose ("Blender versions prior to 2.83.19, 2.93.8 and 3.1"),
|
|
which the exact-version fallback would read as a version.
|
|
|
|
Fixtures are the real affected[] blocks from the records.
|
|
"""
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import zipfile
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from app.services import app_cve_scanner_service as cpe # noqa: E402
|
|
from app.services import cvelistv5_scan_service as c5 # noqa: E402
|
|
|
|
B_60103 = {"defaultStatus": "affected", "vendor": "blender", "product": "blender",
|
|
"repo": "https://projects.blender.org/blender/blender",
|
|
"versions": [{"status": "affected", "version": "3.0.0", "versionType": "semver",
|
|
"lessThanOrEqual": "5.1.2"},
|
|
{"status": "unaffected", "version": "968972a918b5ed2d534295b639c54449d7de11cd",
|
|
"versionType": "git"}]}
|
|
# CVE-2026-119718..119722 (published 2026-10-30): another CNA, vendor spelled
|
|
# "Blender Foundation", two lines: everything before the 4.5 LTS fix, and 5.x
|
|
# before 5.2.3. All five carry this same block.
|
|
NEW = [f"CVE-2026-1197{n}" for n in (18, 19, 20, 21, 22)]
|
|
B_119722 = {"vendor": "Blender Foundation", "product": "Blender", "defaultStatus": "unaffected",
|
|
"repo": "https://projects.blender.org/blender/blender",
|
|
"versions": [{"version": "0", "lessThan": "4.5.15", "status": "affected",
|
|
"versionType": "semver"},
|
|
{"version": "5.0.0", "lessThan": "5.2.3", "status": "affected",
|
|
"versionType": "semver"}]}
|
|
# CVE-2017-2899: one exact release.
|
|
B_2899 = {"vendor": "Blender", "product": "Blender",
|
|
"versions": [{"status": "affected", "version": "v2.78c"}]}
|
|
# CVE-2022-0544: prose under vendor "n/a", stays out of the index.
|
|
B_0544 = {"vendor": "n/a", "product": "Blender",
|
|
"versions": [{"status": "affected",
|
|
"version": "Blender versions prior to 2.83.19, 2.93.8 and 3.1"}]}
|
|
|
|
VENDOR = "Blender Foundation"
|
|
|
|
|
|
class FakeAsset:
|
|
id = 1
|
|
operating_system = "Windows 11 Enterprise"
|
|
|
|
|
|
def _build_index():
|
|
"""Run the REAL index build over a ZIP shaped like cvelistV5's."""
|
|
fd, path = tempfile.mkstemp(suffix=".zip")
|
|
os.close(fd)
|
|
with zipfile.ZipFile(path, "w") as zf:
|
|
for cve_id, aff in (("CVE-2026-60103", B_60103), ("CVE-2017-2899", B_2899),
|
|
("CVE-2022-0544", B_0544), *((c, B_119722) for c in NEW)):
|
|
year, num = cve_id.split("-")[1:]
|
|
zf.writestr(f"cvelistV5-main/cves/{year}/{num[:-3]}xxx/{cve_id}.json",
|
|
json.dumps({"cveMetadata": {"cveId": cve_id},
|
|
"containers": {"cna": {"affected": [aff]}}}))
|
|
orig = (c5._ZIP_PATH, c5._ensure_zip, c5._store_index, c5._merge_mozilla_mfsa,
|
|
c5._merge_chrome_releases)
|
|
c5._ZIP_PATH = path
|
|
c5._ensure_zip = lambda force=False: True
|
|
c5._store_index = lambda db, index: None
|
|
c5._merge_mozilla_mfsa = lambda *a, **kw: None
|
|
c5._merge_chrome_releases = lambda *a, **kw: None
|
|
try:
|
|
return c5.build_product_index(None)
|
|
finally:
|
|
(c5._ZIP_PATH, c5._ensure_zip, c5._store_index, c5._merge_mozilla_mfsa,
|
|
c5._merge_chrome_releases) = orig
|
|
os.unlink(path)
|
|
|
|
|
|
def _scan(index, name, version, os_name="Windows 11 Enterprise"):
|
|
"""Run the REAL scan_asset, capturing what it would upsert."""
|
|
found = []
|
|
orig = cpe._upsert
|
|
cpe._upsert = lambda db, asset, name, version, c, new_ids, **kw: (
|
|
found.append((c["cve"], c["fixed"])), new_ids.append(len(new_ids)))
|
|
asset = FakeAsset()
|
|
asset.operating_system = os_name
|
|
try:
|
|
c5.scan_asset(None, asset, [{"name": name, "version": version, "vendor": VENDOR}],
|
|
index, touched=set())
|
|
finally:
|
|
cpe._upsert = orig
|
|
return sorted(found)
|
|
|
|
|
|
def demo():
|
|
# --- resolution ---------------------------------------------------------
|
|
for name in ("Blender", "blender", "Blender 4.2 LTS", "Blender 5.0"):
|
|
assert c5.resolve(name) == "blender", name
|
|
for other in ("Blender Launcher", "BlenderKit", "Blender Benchmark Launcher"):
|
|
assert c5.resolve(other) is None, other
|
|
assert c5._pair_key("blender", "blender") == "blender"
|
|
assert c5._pair_key("Blender", "Blender") == "blender"
|
|
assert c5._pair_key("Blender Foundation", "Blender") == "blender"
|
|
assert c5._pair_key("n/a", "Blender") is None
|
|
|
|
# --- end to end: index build → scan --------------------------------------
|
|
index = _build_index()
|
|
assert sorted({e["cve"] for e in index["blender"]}) == sorted(
|
|
["CVE-2017-2899", "CVE-2026-60103", *NEW])
|
|
|
|
def new(fix):
|
|
return [(c, fix) for c in NEW]
|
|
on_5x = sorted(new("5.2.3") + [("CVE-2026-60103", ">5.1.2")])
|
|
on_lts = sorted(new("4.5.15") + [("CVE-2026-60103", ">5.1.2")])
|
|
# The reference hosts: Intune 5.0.0, Wazuh 5.0.1.
|
|
assert _scan(index, "Blender", "5.0.0") == on_5x
|
|
assert _scan(index, "Blender", "5.0.1") == on_5x
|
|
assert _scan(index, "Blender", "5.1.2") == on_5x
|
|
# Between the two bounds: only the 5.x line of the new records.
|
|
assert _scan(index, "Blender", "5.1.3") == new("5.2.3")
|
|
assert _scan(index, "Blender", "5.2.2") == new("5.2.3")
|
|
assert _scan(index, "Blender", "5.2.3") == []
|
|
# The 4.5 LTS line: fixed in 4.5.15, so the fix shown is that one, not 5.2.3.
|
|
assert _scan(index, "Blender", "4.5.14") == on_lts
|
|
assert _scan(index, "Blender", "4.5.15") == [("CVE-2026-60103", ">5.1.2")]
|
|
assert _scan(index, "Blender", "3.0.0") == on_lts
|
|
assert _scan(index, "Blender", "2.93.18") == new("4.5.15")
|
|
# macOS lists the app as plain "Blender" too.
|
|
assert _scan(index, "Blender", "5.0.1", "macOS 15.5") == on_5x
|
|
# A distro package: "+dfsg-2" is trimmed to 4.3.2, which says nothing about
|
|
# backports. Linux is Wazuh's.
|
|
for os_name in ("Debian GNU/Linux 13", "Ubuntu 24.04.3 LTS", "Linux"):
|
|
assert _scan(index, "blender", "4.3.2+dfsg-2", os_name) == [], os_name
|
|
|
|
print("ok blender resolves under both vendor spellings, both ranges of CVE-2026-119718..119722 hold")
|
|
|
|
|
|
def test_blender():
|
|
demo()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
demo()
|