From 787eccc34e7920f70b44478f60a1980e85b0dd9c Mon Sep 17 00:00:00 2001 From: Akash Rajeev Date: Sun, 18 Oct 2026 16:04:29 +0530 Subject: [PATCH] fix: collect affected range for GitLab advisories with tricky Maven ranges Some Maven affected_range values in the GitLab advisories fail to parse (single point ranges like [1.81.0,1.81.0], or segments out of version order), so the affected range gets dropped entirely. Parse the range segment by segment as a fallback and combine the constraints, so these advisories keep their affected range. Reference: https://github.com/aboutcode-org/vulnerablecode/issues/2412 Signed-off-by: Akash Rajeev --- .../pipelines/v2_importers/gitlab_importer.py | 34 ++++++++- .../v2_importers/test_gitlab_importer_v2.py | 74 ++++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/gitlab_importer.py b/vulnerabilities/pipelines/v2_importers/gitlab_importer.py index ce9ff91cb..4f8a819d9 100644 --- a/vulnerabilities/pipelines/v2_importers/gitlab_importer.py +++ b/vulnerabilities/pipelines/v2_importers/gitlab_importer.py @@ -9,6 +9,7 @@ import json import logging +import re import traceback from pathlib import Path from typing import Iterable @@ -178,6 +179,37 @@ def get_purl(package_slug, purl_type_by_gitlab_scheme, logger): return +def parse_native_version_range(vrc, native_range): + """ + Return a VersionRange parsed from a native version range string. + + Some upstream ranges cannot be parsed in a single pass, for example Maven + ranges containing single-point segments such as ``[1.81.0,1.81.0]`` or + segments that are not listed in version order. In this case, parse each + segment separately and combine the resulting constraints in a single range. + """ + try: + return vrc.from_native(native_range) + except Exception: + pass + + segments = re.findall(r"[\[\(][^\[\]()]*[\]\)]|[^,\[\]()]+", native_range) + constraints = [] + for segment in segments: + segment = segment.strip() + single_point = re.fullmatch(r"\[\s*(.+?)\s*,\s*(.+?)\s*\]", segment) + if single_point and single_point.group(1) == single_point.group(2): + segment_range = vrc.from_versions([single_point.group(1)]) + elif segment.startswith(("[", "(")): + segment_range = vrc.from_native(segment) + else: + segment_range = vrc.from_versions([segment]) + constraints.extend(segment_range.constraints) + + constraints = sorted(constraints, key=lambda constraint: constraint.version) + return vrc(constraints=tuple(constraints)) + + def parse_gitlab_advisory( file, base_path, gitlab_scheme_by_purl_type, purl_type_by_gitlab_scheme, logger ): @@ -266,7 +298,7 @@ def parse_gitlab_advisory( gitlab_scheme=gitlab_scheme, string=affected_range ) else: - affected_version_range = vrc.from_native(affected_range) + affected_version_range = parse_native_version_range(vrc, affected_range) except Exception as e: logger( f"parse_yaml_file: affected_range is not parsable: {affected_range!r} for: {purl!s} error: {e!r}\n {traceback.format_exc()}", diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_gitlab_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_gitlab_importer_v2.py index 070d2f694..d67481461 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_gitlab_importer_v2.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_gitlab_importer_v2.py @@ -38,8 +38,7 @@ def mock_gitlab_yaml(tmp_path): advisory_dir.mkdir(parents=True) advisory_file = advisory_dir / "CVE-2022-0001.yml" - advisory_file.write_text( - """ + advisory_file.write_text(""" identifier: "CVE-2022-0001" package_slug: "pypi/package_name" title: "Example vulnerability" @@ -54,8 +53,7 @@ def mock_gitlab_yaml(tmp_path): - "CWE-79" identifiers: - "CVE-2022-0001" - """ - ) + """) return tmp_path @@ -206,3 +204,71 @@ def mock_get_purl(package_slug, purl_type_by_gitlab_scheme, logger): assert isinstance(result.date_published, datetime) assert result.date_published.year == 2018 assert result.affected_packages == [] # Because get_purl was mocked to return None + + +@pytest.fixture +def gitlab_maven_advisory_yaml(tmp_path): + content = { + "identifier": "CVE-2025-14813", + "package_slug": "maven/org.bouncycastle/bcprov-debug-jdk14", + "title": "Example maven vulnerability", + "description": "Example description", + "pubdate": "2025-01-15", + "affected_range": "[1.59.0,1.80.1],[1.82.0,1.84.0),[1.81.0,1.81.0]", + "fixed_versions": ["1.84.0"], + "urls": ["https://example.com/advisory"], + "identifiers": ["CVE-2025-14813"], + } + + advisory_path = tmp_path / "CVE-2025-14813.yml" + advisory_path.write_text(saneyaml.dump(content)) + return advisory_path, content + + +def parse_maven_advisory(file_path): + from vulnerabilities.pipelines.v2_importers.gitlab_importer import GitLabImporterPipeline + + dummy_logger = lambda *args, **kwargs: None # Ignore logging in test + + return parse_gitlab_advisory( + file=file_path, + base_path=file_path.parent, + gitlab_scheme_by_purl_type=GitLabImporterPipeline.gitlab_scheme_by_purl_type, + purl_type_by_gitlab_scheme=GitLabImporterPipeline.purl_type_by_gitlab_scheme, + logger=dummy_logger, + ) + + +def test_parse_gitlab_advisory_maven_multirange(gitlab_maven_advisory_yaml): + file_path, _ = gitlab_maven_advisory_yaml + + result = parse_maven_advisory(file_path) + + assert isinstance(result, AdvisoryDataV2) + affected_package = result.affected_packages[0] + assert affected_package.package.name == "bcprov-debug-jdk14" + assert affected_package.package.namespace == "org.bouncycastle" + assert str(affected_package.affected_version_range) == ( + "vers:maven/>=1.59.0|<=1.80.1|1.81.0|>=1.82.0|<1.84.0" + ) + assert str(affected_package.fixed_version_range) == "vers:maven/1.84.0" + + from univers.versions import MavenVersion + + affected_range = affected_package.affected_version_range + for version in ["1.59.0", "1.70.0", "1.81.0", "1.83.0"]: + assert MavenVersion(version) in affected_range + for version in ["1.80.2", "1.81.5", "1.84.0", "1.90.0"]: + assert MavenVersion(version) not in affected_range + + +def test_parse_gitlab_advisory_maven_single_point_range(gitlab_maven_advisory_yaml): + file_path, content = gitlab_maven_advisory_yaml + + content["affected_range"] = "[1.81.0,1.81.0]" + file_path.write_text(saneyaml.dump(content)) + + result = parse_maven_advisory(file_path) + + assert isinstance(result, AdvisoryDataV2) + assert str(result.affected_packages[0].affected_version_range) == "vers:maven/1.81.0"