fix(scan): a companion product is not the product it names

"Veeam Explorer for Microsoft Teams" 13.3.2.3 collected four Teams CVEs whose
bounds are 25122.x — Veeam's own software, matched purely because its name
contains "Microsoft Teams". Checking the rest of the registry against the same
shape found four more: Chrome findings on "Citrix Workspace app for Google
Chrome", Firefox findings on "Kaspersky Plugin for Mozilla Firefox", and so on.
Backup tools, plugins and connectors are all named this way.

The product being described stands FIRST, so a match that lands only in the
"…for <product>" tail is dropped. Matching itself still runs on the full name,
which matters: the Firefox entry rejects "Firefox for iOS" itself (WebKit, not
Gecko, different CVEs), and trimming the name before matching hid that from it
— caught by test_mobile_browsers.

Teams is anchored to the start of the name as well, so a product that merely
mentions it later cannot match even without a "for". Both scanner paths share
the rule, so they cannot disagree about the same install.
This commit is contained in:
2026-08-10 14:18:56 +02:00
parent 591e27bbe9
commit 93120f5dd6
3 changed files with 96 additions and 2 deletions
+58
View File
@@ -0,0 +1,58 @@
""""<x> for <y>" is a companion product, not the product itself.
"Veeam Explorer for Microsoft Teams" 13.3.2.3 collected four Teams CVEs whose
bounds are 25122.x — Veeam's software, matched because its name contains
"Microsoft Teams". Backup tools, plugins and connectors are all named this way,
and the same shape put Chrome findings on "Citrix Workspace app for Google
Chrome" and Firefox findings on "Kaspersky Plugin for Mozilla Firefox".
The product being described always comes FIRST, so only that part is matched.
"""
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.services.app_cve_scanner_service import ( # noqa: E402
companion_prefix, resolve_product)
from app.services import cvelistv5_scan_service as c5 # noqa: E402
def demo():
# The split itself.
assert companion_prefix("Veeam Explorer for Microsoft Teams") == "Veeam"
assert companion_prefix("Citrix Workspace app for Google Chrome") == "Citrix Workspace"
assert companion_prefix("Microsoft Teams") == "Microsoft Teams"
# No "for" → untouched, including names with punctuation and editions.
for name in ("Microsoft Visual C++ 2013 Redistributable (x86) - 12.0.21005",
"7-Zip 19.00 (x64 edition)", "Adobe Acrobat (64-bit)"):
assert companion_prefix(name) == name, name
# Companions must not resolve to the product they name.
for name in ("Veeam Explorer for Microsoft Teams",
"Veeam Backup for Microsoft 365",
"Citrix Workspace app for Google Chrome",
"Kaspersky Plugin for Mozilla Firefox",
"Nagios Plugin for Node.js"):
assert resolve_product(name) is None, name
assert c5.resolve(name) is None, name
# The real products still resolve on both paths.
for name, cpe_key, c5_key in (
("Microsoft Teams", "cpe:microsoft:teams", "teams"),
("Google Chrome", "cpe:google:chrome", "chrome"),
("Mozilla Firefox", "cpe:mozilla:firefox", "firefox"),
("Node.js", "cpe:nodejs:node.js", "nodejs"),
("7-Zip 19.00 (x64 edition)", "cpe:7-zip:7-zip", "7-zip")):
assert (resolve_product(name) or {}).get("key") == cpe_key, name
assert c5.resolve(name) == c5_key, name
# Teams is also anchored now: the name has to START with it, so a product
# that merely mentions Teams later cannot match even without a "for".
assert resolve_product("Some Vendor Microsoft Teams Toolkit") is None
print("ok companion products no longer inherit the product's CVEs")
if __name__ == "__main__":
demo()