From d8463ebe992aea60585bdedc094e35bf236f2ad1 Mon Sep 17 00:00:00 2001 From: Ariel Schulz Date: Thu, 16 Apr 2026 11:30:55 +0200 Subject: [PATCH 1/2] Improve VulnerabilityMatcher --- doc/changes/unreleased.md | 4 +++ .../dependencies/track_vulnerabilities.py | 13 +++++---- .../track_vulnerabilities_test.py | 28 +++++++++++++++++++ 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/doc/changes/unreleased.md b/doc/changes/unreleased.md index fb4737052..904212661 100644 --- a/doc/changes/unreleased.md +++ b/doc/changes/unreleased.md @@ -1,3 +1,7 @@ # Unreleased ## Summary + +## Features + +* #777: Improved VulnerabilityMatcher to handle packages with multiple vulnerabilities diff --git a/exasol/toolbox/util/dependencies/track_vulnerabilities.py b/exasol/toolbox/util/dependencies/track_vulnerabilities.py index db074ba3e..c7e5e534b 100644 --- a/exasol/toolbox/util/dependencies/track_vulnerabilities.py +++ b/exasol/toolbox/util/dependencies/track_vulnerabilities.py @@ -1,3 +1,4 @@ +from collections import defaultdict from inspect import cleandoc from pydantic import ( @@ -12,10 +13,10 @@ class VulnerabilityMatcher: def __init__(self, current_vulnerabilities: list[Vulnerability]): # Dict of current vulnerabilities: # * keys: package names - # * values: set of each vulnerability's references - self._references = { - v.package.name: set(v.references) for v in current_vulnerabilities - } + # * values: set of each vulnerability's references combined + self._references = defaultdict(set) + for v in current_vulnerabilities: + self._references[v.package.name].update(v.references) def is_resolved(self, vuln: Vulnerability) -> bool: """ @@ -34,8 +35,8 @@ def is_resolved(self, vuln: Vulnerability) -> bool: vulnerability. """ refs = set(vuln.references) - current = self._references.get(vuln.package.name, set()) - return not refs.intersection(current) + current_refs = self._references.get(vuln.package.name, set()) + return refs.isdisjoint(current_refs) class DependenciesAudit(BaseModel): diff --git a/test/unit/util/dependencies/track_vulnerabilities_test.py b/test/unit/util/dependencies/track_vulnerabilities_test.py index 5ea3911f1..3cbc8f291 100644 --- a/test/unit/util/dependencies/track_vulnerabilities_test.py +++ b/test/unit/util/dependencies/track_vulnerabilities_test.py @@ -56,6 +56,34 @@ def test_resolved(self, sample_vulnerability): matcher = VulnerabilityMatcher(current_vulnerabilities=[]) assert matcher.is_resolved(vuln) + def test_no_resolution_same_package(self): + """ + Scenario: 'cryptography' has two vulnerabilities. + One is resolved (removed from the current list), the other remains. + """ + pkg_data = {"name": "cryptography", "version": "46.0.6"} + + vuln_1 = Vulnerability( + package=pkg_data, + id="GHSA-m959-cc7f-wv43", + aliases=["CVE-2026-34073"], + fix_versions=["46.0.6"], + description="Dummy description", + ) + + vuln_2 = Vulnerability( + package=pkg_data, + id="GHSA-p423-j2cm-9vmq", + aliases=["CVE-2026-39892"], + fix_versions=["46.0.7"], + description="Dummy description", + ) + + matcher = VulnerabilityMatcher(current_vulnerabilities=[vuln_1, vuln_2]) + + assert matcher.is_resolved(vuln_1) is False + assert matcher.is_resolved(vuln_2) is False + class TestDependenciesAudit: def test_no_vulnerabilities_for_previous_and_current(self): From 78aaac61182503470fbf81c2e85f6b95df05460f Mon Sep 17 00:00:00 2001 From: Ariel Schulz Date: Thu, 16 Apr 2026 11:41:32 +0200 Subject: [PATCH 2/2] Update docstrings --- exasol/toolbox/util/dependencies/track_vulnerabilities.py | 5 ++--- test/unit/util/dependencies/track_vulnerabilities_test.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exasol/toolbox/util/dependencies/track_vulnerabilities.py b/exasol/toolbox/util/dependencies/track_vulnerabilities.py index c7e5e534b..8c9b7c647 100644 --- a/exasol/toolbox/util/dependencies/track_vulnerabilities.py +++ b/exasol/toolbox/util/dependencies/track_vulnerabilities.py @@ -11,9 +11,8 @@ class VulnerabilityMatcher: def __init__(self, current_vulnerabilities: list[Vulnerability]): - # Dict of current vulnerabilities: - # * keys: package names - # * values: set of each vulnerability's references combined + # Dictionary mapping package names to a unified set of all active + # vulnerability references (IDs, CVEs, aliases) for that package. self._references = defaultdict(set) for v in current_vulnerabilities: self._references[v.package.name].update(v.references) diff --git a/test/unit/util/dependencies/track_vulnerabilities_test.py b/test/unit/util/dependencies/track_vulnerabilities_test.py index 3cbc8f291..4feeaec05 100644 --- a/test/unit/util/dependencies/track_vulnerabilities_test.py +++ b/test/unit/util/dependencies/track_vulnerabilities_test.py @@ -39,7 +39,7 @@ def test_changed_id_not_resolved( self, sample_vulnerability, flipped_id_vulnerability ): """ - Simulate a vulnerability to be still present, but it's ID having + Simulate a vulnerability to be still present, but its ID having changed over time. The test verifies that the vulnerability (using the original ID) is