From 2968c93cf54c215a29b8cbcc211692486bdec1ab Mon Sep 17 00:00:00 2001 From: Will Murphy Date: Thu, 23 Apr 2026 16:13:23 -0400 Subject: [PATCH] fix(comparison): stop counting None as a common alias Before, yardstick UI could sometimes report two GHSAs affecting the same package as part of "the same match" if both GHSAs had None as an alias. Signed-off-by: Will Murphy --- src/yardstick/label.py | 12 ++++------ tests/unit/test_label.py | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/src/yardstick/label.py b/src/yardstick/label.py index 483d2c86..72ea3107 100644 --- a/src/yardstick/label.py +++ b/src/yardstick/label.py @@ -81,14 +81,10 @@ def find_labels_for_match( # noqa: PLR0913, PLR0912, C901 def has_overlapping_vulnerability_id(label_entry: LabelEntry, match: Match) -> bool: - left_ids = {label_entry.vulnerability_id, label_entry.effective_cve} - right_ids = {match.vulnerability.id, match.vulnerability.cve_id} - - if "" in left_ids: - left_ids.remove("") - - if "" in right_ids: - right_ids.remove("") + # drop None and "" so a missing effective_cve on the label and a missing + # cve_id on the match don't intersect as a shared "token" + left_ids = {label_entry.vulnerability_id, label_entry.effective_cve} - {None, ""} + right_ids = {match.vulnerability.id, match.vulnerability.cve_id} - {None, ""} return bool(left_ids & right_ids) diff --git a/tests/unit/test_label.py b/tests/unit/test_label.py index 3112a841..40e99868 100644 --- a/tests/unit/test_label.py +++ b/tests/unit/test_label.py @@ -3,6 +3,7 @@ from yardstick.label import ( _contains_as_value, find_labels_for_match, + has_overlapping_vulnerability_id, merge_label_entries, ) @@ -192,6 +193,17 @@ def label_entries(self): ), ["5"], ), + # case: match on a non-CVE vuln id (e.g. GHSA) must not pull in labels + # for *different* non-CVE vuln ids on the same package just because + # both sides have no effective_cve / cve_id + ( + "my/image:latest", + artifact.Match( + vulnerability=artifact.Vulnerability(id="GHSA-xxxx-yyyy-zzzz"), + package=artifact.Package(name="package", version="1.0"), + ), + [], + ), ], ) def test_find_labels_for_match( @@ -205,6 +217,44 @@ def test_find_labels_for_match( assert expected_label_ids == ids +class TestHasOverlappingVulnerabilityID: + @pytest.mark.parametrize( + ("label_vuln_id", "label_effective_cve", "match_vuln_id", "match_cve_id", "expected"), + [ + # same CVE on both sides + ("CVE-2020-0001", None, "CVE-2020-0001", "CVE-2020-0001", True), + # same GHSA on both sides (no CVEs known) + ("GHSA-aaaa-bbbb-cccc", None, "GHSA-aaaa-bbbb-cccc", None, True), + # GHSA on the label aliases to a CVE that the match also carries + ("GHSA-aaaa-bbbb-cccc", "CVE-2020-0001", "CVE-2020-0001", "CVE-2020-0001", True), + # GHSA on the match aliases to a CVE that the label also carries + ("CVE-2020-0001", None, "GHSA-aaaa-bbbb-cccc", "CVE-2020-0001", True), + # different non-CVE ids with no CVE aliases on either side must NOT overlap + # (regression: previously both sides carried None and set-intersected on it) + ("GHSA-aaaa-bbbb-cccc", None, "GHSA-xxxx-yyyy-zzzz", None, False), + # same guard, but with empty strings instead of None + ("GHSA-aaaa-bbbb-cccc", "", "GHSA-xxxx-yyyy-zzzz", "", False), + # completely disjoint CVE ids + ("CVE-2020-0001", None, "CVE-2020-0002", "CVE-2020-0002", False), + ], + ) + def test_overlap(self, label_vuln_id, label_effective_cve, match_vuln_id, match_cve_id, expected): + label_entry = artifact.LabelEntry( + label=artifact.Label.FalsePositive, + vulnerability_id=label_vuln_id, + effective_cve=label_effective_cve, + package=artifact.Package(name="package", version="1.0"), + source="manual", + user="somebody", + ID="x", + ) + match = artifact.Match( + vulnerability=artifact.Vulnerability(id=match_vuln_id, cve_id=match_cve_id), + package=artifact.Package(name="package", version="1.0"), + ) + assert has_overlapping_vulnerability_id(label_entry, match) is expected + + class TestMergeLabelEntries: @pytest.fixture() def existing_label_entries(self):