From 0bb18b58d151b4582741b1b3434589235f984d33 Mon Sep 17 00:00:00 2001 From: Sampurna Pyne Date: Mon, 5 Jan 2026 02:11:18 +0530 Subject: [PATCH 1/4] Add V2 Importer for Tuxcare Signed-off-by: Sampurna Pyne --- vulnerabilities/importers/__init__.py | 2 + .../v2_importers/tuxcare_importer.py | 114 ++++++++++++++++++ .../v2_importers/test_tuxcare_importer_v2.py | 48 ++++++++ .../tests/test_data/tuxcare/data.json | 52 ++++++++ .../tests/test_data/tuxcare/expected.json | 102 ++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 vulnerabilities/pipelines/v2_importers/tuxcare_importer.py create mode 100644 vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py create mode 100644 vulnerabilities/tests/test_data/tuxcare/data.json create mode 100644 vulnerabilities/tests/test_data/tuxcare/expected.json diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 8aa9961d5..031d8c1a2 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -65,6 +65,7 @@ from vulnerabilities.pipelines.v2_importers import ruby_importer as ruby_importer_v2 from vulnerabilities.pipelines.v2_importers import vulnrichment_importer as vulnrichment_importer_v2 from vulnerabilities.pipelines.v2_importers import xen_importer as xen_importer_v2 +from vulnerabilities.pipelines.v2_importers import tuxcare_importer as tuxcare_importer_v2 from vulnerabilities.utils import create_registry IMPORTERS_REGISTRY = create_registry( @@ -90,6 +91,7 @@ ruby_importer_v2.RubyImporterPipeline, epss_importer_v2.EPSSImporterPipeline, mattermost_importer_v2.MattermostImporterPipeline, + tuxcare_importer_v2.TuxCareImporterPipeline, nvd_importer.NVDImporterPipeline, github_importer.GitHubAPIImporterPipeline, gitlab_importer.GitLabImporterPipeline, diff --git a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py new file mode 100644 index 000000000..a49d642e0 --- /dev/null +++ b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py @@ -0,0 +1,114 @@ +import json +import logging +from typing import Iterable + +from dateutil import parser as date_parser +from django.utils import timezone +from packageurl import PackageURL +from univers.version_range import GenericVersionRange + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.importer import AffectedPackageV2 +from vulnerabilities.importer import ReferenceV2 +from vulnerabilities.importer import VulnerabilitySeverity +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.severity_systems import GENERIC +from vulnerabilities.utils import fetch_response + +logger = logging.getLogger(__name__) + + +class TuxCareImporterPipeline(VulnerableCodeBaseImporterPipelineV2): + pipeline_id = "tuxcare_importer_v2" + spdx_license_expression = "Apache-2.0" + license_url = "https://tuxcare.com/legal" + url = "https://cve.tuxcare.com/els/download-json?orderBy=updated-desc" + + @classmethod + def steps(cls): + return (cls.collect_and_store_advisories,) + + def advisories_count(self) -> int: + response = fetch_response(self.url) + data = response.json() if response else [] + return len(data) + + def collect_advisories(self) -> Iterable[AdvisoryData]: + response = fetch_response(self.url) + if not response: + return + + data = response.json() + if not data: + return + + for record in data: + cve_id = record.get("cve", "").strip() + if not cve_id or not cve_id.startswith("CVE-"): + continue + + os_name = record.get("os_name", "").strip() + project_name = record.get("project_name", "").strip() + version = record.get("version", "").strip() + score = record.get("score", "").strip() + severity = record.get("severity", "").strip() + status = record.get("status", "").strip() + last_updated = record.get("last_updated", "").strip() + + safe_os = os_name.replace(" ", "_") if os_name else "unknown" + advisory_id = f"TUXCARE-{cve_id}-{safe_os}-{project_name}" + + summary = f"TuxCare advisory for {cve_id}" + if project_name: + summary += f" in {project_name}" + if os_name: + summary += f" on {os_name}" + + affected_packages = [] + if project_name: + purl = PackageURL(type="generic", name=project_name) + + affected_version_range = None + if version: + try: + affected_version_range = GenericVersionRange.from_versions([version]) + except Exception: + pass + + affected_packages.append( + AffectedPackageV2( + package=purl, + affected_version_range=affected_version_range, + ) + ) + + severities = [] + if severity and score: + severities.append( + VulnerabilitySeverity( + system=GENERIC, + value=f"{severity} ({score})", + scoring_elements=f"score={score},severity={severity}", + ) + ) + + date_published = None + if last_updated: + try: + date_published = date_parser.parse(last_updated) + if timezone.is_naive(date_published): + date_published = timezone.make_aware(date_published, timezone=timezone.utc) + except Exception: + pass + + yield AdvisoryData( + advisory_id=advisory_id, + aliases=[cve_id], + summary=summary, + affected_packages=affected_packages, + references_v2=[ReferenceV2(url="https://cve.tuxcare.com/")], + severities=severities, + date_published=date_published, + url="https://cve.tuxcare.com/", + original_advisory_text=json.dumps(record, indent=2, ensure_ascii=False), + ) diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py new file mode 100644 index 000000000..98d5dc5af --- /dev/null +++ b/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py @@ -0,0 +1,48 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# VulnerableCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/aboutcode-org/vulnerablecode for support or download. +# See https://aboutcode.org for more information about nexB OSS projects. +# + +import json +from pathlib import Path +from unittest import TestCase +from unittest.mock import Mock +from unittest.mock import patch + +from vulnerabilities.pipelines.v2_importers.tuxcare_importer import TuxCareImporterPipeline +from vulnerabilities.tests import util_tests + +TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "tuxcare" + + +class TestTuxCareImporterPipeline(TestCase): + @patch("vulnerabilities.pipelines.v2_importers.tuxcare_importer.fetch_response") + def test_collect_advisories(self, mock_fetch): + """Test collecting and parsing advisories from test data""" + sample_path = TEST_DATA / "data.json" + sample_data = json.loads(sample_path.read_text(encoding="utf-8")) + + mock_fetch.return_value = Mock(json=lambda: sample_data) + + pipeline = TuxCareImporterPipeline() + advisories = [data.to_dict() for data in list(pipeline.collect_advisories())] + + expected_file = TEST_DATA / "expected.json" + util_tests.check_results_against_json(advisories, expected_file) + + @patch("vulnerabilities.pipelines.v2_importers.tuxcare_importer.fetch_response") + def test_advisories_count(self, mock_fetch): + """Test counting advisories""" + sample_path = TEST_DATA / "data.json" + sample_data = json.loads(sample_path.read_text(encoding="utf-8")) + + mock_fetch.return_value = Mock(json=lambda: sample_data) + + pipeline = TuxCareImporterPipeline() + count = pipeline.advisories_count() + + assert count == 5 diff --git a/vulnerabilities/tests/test_data/tuxcare/data.json b/vulnerabilities/tests/test_data/tuxcare/data.json new file mode 100644 index 000000000..0bb656622 --- /dev/null +++ b/vulnerabilities/tests/test_data/tuxcare/data.json @@ -0,0 +1,52 @@ +[ + { + "cve": "CVE-2023-52922", + "os_name": "CloudLinux 7 ELS", + "project_name": "squid", + "version": "3.5.20", + "score": "7.8", + "severity": "HIGH", + "status": "In Testing", + "last_updated": "2025-12-23 10:08:36.423446" + }, + { + "cve": "CVE-2023-52922", + "os_name": "Oracle Linux 7 ELS", + "project_name": "squid", + "version": "3.5.20", + "score": "7.8", + "severity": "HIGH", + "status": "In Testing", + "last_updated": "2025-12-23 10:08:35.944749" + }, + { + "cve": "CVE-2023-48161", + "os_name": "RHEL 7 ELS", + "project_name": "java-11-openjdk", + "version": "11.0.23", + "score": "7.1", + "severity": "HIGH", + "status": "In Progress", + "last_updated": "2025-12-23 08:55:12.096092" + }, + { + "cve": "CVE-2024-21147", + "os_name": "RHEL 7 ELS", + "project_name": "java-11-openjdk", + "version": "11.0.23", + "score": "7.4", + "severity": "HIGH", + "status": "In Progress", + "last_updated": "2025-12-23 08:55:07.139188" + }, + { + "cve": "CVE-2025-21587", + "os_name": "RHEL 7 ELS", + "project_name": "java-11-openjdk", + "version": "11.0.23", + "score": "7.4", + "severity": "HIGH", + "status": "In Progress", + "last_updated": "2025-12-23 08:55:06.706873" + } +] diff --git a/vulnerabilities/tests/test_data/tuxcare/expected.json b/vulnerabilities/tests/test_data/tuxcare/expected.json new file mode 100644 index 000000000..e7488c57e --- /dev/null +++ b/vulnerabilities/tests/test_data/tuxcare/expected.json @@ -0,0 +1,102 @@ +[ + { + "advisory_id": "TUXCARE-CVE-2023-52922-CloudLinux_7_ELS-squid", + "aliases": ["CVE-2023-52922"], + "summary": "TuxCare advisory for CVE-2023-52922 in squid on CloudLinux 7 ELS", + "affected_packages": [ + { + "package": {"type": "generic", "namespace": "", "name": "squid", "version": "", "qualifiers": "", "subpath": ""}, + "affected_version_range": "vers:generic/3.5.20", + "fixed_version_range": null, + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "patches": [], + "severities": [{"system": "generic_textual", "value": "HIGH (7.8)", "scoring_elements": "score=7.8,severity=HIGH"}], + "date_published": "2025-12-23T10:08:36.423446+00:00", + "weaknesses": [], + "url": "https://cve.tuxcare.com/" + }, + { + "advisory_id": "TUXCARE-CVE-2023-52922-Oracle_Linux_7_ELS-squid", + "aliases": ["CVE-2023-52922"], + "summary": "TuxCare advisory for CVE-2023-52922 in squid on Oracle Linux 7 ELS", + "affected_packages": [ + { + "package": {"type": "generic", "namespace": "", "name": "squid", "version": "", "qualifiers": "", "subpath": ""}, + "affected_version_range": "vers:generic/3.5.20", + "fixed_version_range": null, + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "patches": [], + "severities": [{"system": "generic_textual", "value": "HIGH (7.8)", "scoring_elements": "score=7.8,severity=HIGH"}], + "date_published": "2025-12-23T10:08:35.944749+00:00", + "weaknesses": [], + "url": "https://cve.tuxcare.com/" + }, + { + "advisory_id": "TUXCARE-CVE-2023-48161-RHEL_7_ELS-java-11-openjdk", + "aliases": ["CVE-2023-48161"], + "summary": "TuxCare advisory for CVE-2023-48161 in java-11-openjdk on RHEL 7 ELS", + "affected_packages": [ + { + "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "affected_version_range": "vers:generic/11.0.23", + "fixed_version_range": null, + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "patches": [], + "severities": [{"system": "generic_textual", "value": "HIGH (7.1)", "scoring_elements": "score=7.1,severity=HIGH"}], + "date_published": "2025-12-23T08:55:12.096092+00:00", + "weaknesses": [], + "url": "https://cve.tuxcare.com/" + }, + { + "advisory_id": "TUXCARE-CVE-2024-21147-RHEL_7_ELS-java-11-openjdk", + "aliases": ["CVE-2024-21147"], + "summary": "TuxCare advisory for CVE-2024-21147 in java-11-openjdk on RHEL 7 ELS", + "affected_packages": [ + { + "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "affected_version_range": "vers:generic/11.0.23", + "fixed_version_range": null, + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "patches": [], + "severities": [{"system": "generic_textual", "value": "HIGH (7.4)", "scoring_elements": "score=7.4,severity=HIGH"}], + "date_published": "2025-12-23T08:55:07.139188+00:00", + "weaknesses": [], + "url": "https://cve.tuxcare.com/" + }, + { + "advisory_id": "TUXCARE-CVE-2025-21587-RHEL_7_ELS-java-11-openjdk", + "aliases": ["CVE-2025-21587"], + "summary": "TuxCare advisory for CVE-2025-21587 in java-11-openjdk on RHEL 7 ELS", + "affected_packages": [ + { + "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "affected_version_range": "vers:generic/11.0.23", + "fixed_version_range": null, + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "patches": [], + "severities": [{"system": "generic_textual", "value": "HIGH (7.4)", "scoring_elements": "score=7.4,severity=HIGH"}], + "date_published": "2025-12-23T08:55:06.706873+00:00", + "weaknesses": [], + "url": "https://cve.tuxcare.com/" + } +] From 80394725e0b341c8d50fd88fced45cbf042327fc Mon Sep 17 00:00:00 2001 From: Sampurna Pyne Date: Thu, 8 Jan 2026 02:34:53 +0530 Subject: [PATCH 2/4] Refactor as per review Signed-off-by: Sampurna Pyne --- .../v2_importers/tuxcare_importer.py | 58 ++++++++----------- .../v2_importers/test_tuxcare_importer_v2.py | 16 +---- .../tests/test_data/tuxcare/expected.json | 50 ++++++++-------- 3 files changed, 53 insertions(+), 71 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py index a49d642e0..e93e448d0 100644 --- a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py +++ b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py @@ -1,15 +1,15 @@ import json import logging from typing import Iterable +from typing import Mapping -from dateutil import parser as date_parser -from django.utils import timezone +from dateutil.parser import parse from packageurl import PackageURL +from pytz import UTC from univers.version_range import GenericVersionRange from vulnerabilities.importer import AdvisoryData from vulnerabilities.importer import AffectedPackageV2 -from vulnerabilities.importer import ReferenceV2 from vulnerabilities.importer import VulnerabilitySeverity from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 from vulnerabilities.severity_systems import GENERIC @@ -22,27 +22,25 @@ class TuxCareImporterPipeline(VulnerableCodeBaseImporterPipelineV2): pipeline_id = "tuxcare_importer_v2" spdx_license_expression = "Apache-2.0" license_url = "https://tuxcare.com/legal" - url = "https://cve.tuxcare.com/els/download-json?orderBy=updated-desc" @classmethod def steps(cls): - return (cls.collect_and_store_advisories,) + return ( + cls.fetch, + cls.collect_and_store_advisories, + ) + + def fetch(self) -> Iterable[Mapping]: + url = "https://cve.tuxcare.com/els/download-json?orderBy=updated-desc" + self.log(f"Fetching `{url}`") + response = fetch_response(url) + self.response = response.json() if response else [] def advisories_count(self) -> int: - response = fetch_response(self.url) - data = response.json() if response else [] - return len(data) + return len(self.response) def collect_advisories(self) -> Iterable[AdvisoryData]: - response = fetch_response(self.url) - if not response: - return - - data = response.json() - if not data: - return - - for record in data: + for record in self.response: cve_id = record.get("cve", "").strip() if not cve_id or not cve_id.startswith("CVE-"): continue @@ -52,11 +50,9 @@ def collect_advisories(self) -> Iterable[AdvisoryData]: version = record.get("version", "").strip() score = record.get("score", "").strip() severity = record.get("severity", "").strip() - status = record.get("status", "").strip() last_updated = record.get("last_updated", "").strip() - safe_os = os_name.replace(" ", "_") if os_name else "unknown" - advisory_id = f"TUXCARE-{cve_id}-{safe_os}-{project_name}" + advisory_id = cve_id summary = f"TuxCare advisory for {cve_id}" if project_name: @@ -67,13 +63,13 @@ def collect_advisories(self) -> Iterable[AdvisoryData]: affected_packages = [] if project_name: purl = PackageURL(type="generic", name=project_name) - + affected_version_range = None if version: try: affected_version_range = GenericVersionRange.from_versions([version]) - except Exception: - pass + except ValueError as e: + logger.warning(f"Failed to parse version {version} for {cve_id}: {e}") affected_packages.append( AffectedPackageV2( @@ -87,28 +83,24 @@ def collect_advisories(self) -> Iterable[AdvisoryData]: severities.append( VulnerabilitySeverity( system=GENERIC, - value=f"{severity} ({score})", - scoring_elements=f"score={score},severity={severity}", + value=score, + scoring_elements=severity, ) ) date_published = None if last_updated: try: - date_published = date_parser.parse(last_updated) - if timezone.is_naive(date_published): - date_published = timezone.make_aware(date_published, timezone=timezone.utc) - except Exception: - pass + date_published = parse(last_updated).replace(tzinfo=UTC) + except ValueError as e: + logger.warning(f"Failed to parse date {last_updated} for {cve_id}: {e}") yield AdvisoryData( advisory_id=advisory_id, - aliases=[cve_id], summary=summary, affected_packages=affected_packages, - references_v2=[ReferenceV2(url="https://cve.tuxcare.com/")], severities=severities, date_published=date_published, - url="https://cve.tuxcare.com/", + url=f"https://cve.tuxcare.com/els/cve/{cve_id}", original_advisory_text=json.dumps(record, indent=2, ensure_ascii=False), ) diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py index 98d5dc5af..a0ca0218a 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_tuxcare_importer_v2.py @@ -22,27 +22,17 @@ class TestTuxCareImporterPipeline(TestCase): @patch("vulnerabilities.pipelines.v2_importers.tuxcare_importer.fetch_response") def test_collect_advisories(self, mock_fetch): - """Test collecting and parsing advisories from test data""" sample_path = TEST_DATA / "data.json" sample_data = json.loads(sample_path.read_text(encoding="utf-8")) mock_fetch.return_value = Mock(json=lambda: sample_data) pipeline = TuxCareImporterPipeline() + pipeline.fetch() + advisories = [data.to_dict() for data in list(pipeline.collect_advisories())] expected_file = TEST_DATA / "expected.json" util_tests.check_results_against_json(advisories, expected_file) - @patch("vulnerabilities.pipelines.v2_importers.tuxcare_importer.fetch_response") - def test_advisories_count(self, mock_fetch): - """Test counting advisories""" - sample_path = TEST_DATA / "data.json" - sample_data = json.loads(sample_path.read_text(encoding="utf-8")) - - mock_fetch.return_value = Mock(json=lambda: sample_data) - - pipeline = TuxCareImporterPipeline() - count = pipeline.advisories_count() - - assert count == 5 + assert pipeline.advisories_count() == 5 diff --git a/vulnerabilities/tests/test_data/tuxcare/expected.json b/vulnerabilities/tests/test_data/tuxcare/expected.json index e7488c57e..96287039a 100644 --- a/vulnerabilities/tests/test_data/tuxcare/expected.json +++ b/vulnerabilities/tests/test_data/tuxcare/expected.json @@ -1,7 +1,7 @@ [ { - "advisory_id": "TUXCARE-CVE-2023-52922-CloudLinux_7_ELS-squid", - "aliases": ["CVE-2023-52922"], + "advisory_id": "CVE-2023-52922", + "aliases": [], "summary": "TuxCare advisory for CVE-2023-52922 in squid on CloudLinux 7 ELS", "affected_packages": [ { @@ -12,16 +12,16 @@ "fixed_by_commit_patches": [] } ], - "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "references_v2": [], "patches": [], - "severities": [{"system": "generic_textual", "value": "HIGH (7.8)", "scoring_elements": "score=7.8,severity=HIGH"}], + "severities": [{"system": "generic_textual", "value": "7.8", "scoring_elements": "HIGH"}], "date_published": "2025-12-23T10:08:36.423446+00:00", "weaknesses": [], - "url": "https://cve.tuxcare.com/" + "url": "https://cve.tuxcare.com/els/cve/CVE-2023-52922" }, { - "advisory_id": "TUXCARE-CVE-2023-52922-Oracle_Linux_7_ELS-squid", - "aliases": ["CVE-2023-52922"], + "advisory_id": "CVE-2023-52922", + "aliases": [], "summary": "TuxCare advisory for CVE-2023-52922 in squid on Oracle Linux 7 ELS", "affected_packages": [ { @@ -32,16 +32,16 @@ "fixed_by_commit_patches": [] } ], - "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "references_v2": [], "patches": [], - "severities": [{"system": "generic_textual", "value": "HIGH (7.8)", "scoring_elements": "score=7.8,severity=HIGH"}], + "severities": [{"system": "generic_textual", "value": "7.8", "scoring_elements": "HIGH"}], "date_published": "2025-12-23T10:08:35.944749+00:00", "weaknesses": [], - "url": "https://cve.tuxcare.com/" + "url": "https://cve.tuxcare.com/els/cve/CVE-2023-52922" }, { - "advisory_id": "TUXCARE-CVE-2023-48161-RHEL_7_ELS-java-11-openjdk", - "aliases": ["CVE-2023-48161"], + "advisory_id": "CVE-2023-48161", + "aliases": [], "summary": "TuxCare advisory for CVE-2023-48161 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { @@ -52,16 +52,16 @@ "fixed_by_commit_patches": [] } ], - "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "references_v2": [], "patches": [], - "severities": [{"system": "generic_textual", "value": "HIGH (7.1)", "scoring_elements": "score=7.1,severity=HIGH"}], + "severities": [{"system": "generic_textual", "value": "7.1", "scoring_elements": "HIGH"}], "date_published": "2025-12-23T08:55:12.096092+00:00", "weaknesses": [], - "url": "https://cve.tuxcare.com/" + "url": "https://cve.tuxcare.com/els/cve/CVE-2023-48161" }, { - "advisory_id": "TUXCARE-CVE-2024-21147-RHEL_7_ELS-java-11-openjdk", - "aliases": ["CVE-2024-21147"], + "advisory_id": "CVE-2024-21147", + "aliases": [], "summary": "TuxCare advisory for CVE-2024-21147 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { @@ -72,16 +72,16 @@ "fixed_by_commit_patches": [] } ], - "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "references_v2": [], "patches": [], - "severities": [{"system": "generic_textual", "value": "HIGH (7.4)", "scoring_elements": "score=7.4,severity=HIGH"}], + "severities": [{"system": "generic_textual", "value": "7.4", "scoring_elements": "HIGH"}], "date_published": "2025-12-23T08:55:07.139188+00:00", "weaknesses": [], - "url": "https://cve.tuxcare.com/" + "url": "https://cve.tuxcare.com/els/cve/CVE-2024-21147" }, { - "advisory_id": "TUXCARE-CVE-2025-21587-RHEL_7_ELS-java-11-openjdk", - "aliases": ["CVE-2025-21587"], + "advisory_id": "CVE-2025-21587", + "aliases": [], "summary": "TuxCare advisory for CVE-2025-21587 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { @@ -92,11 +92,11 @@ "fixed_by_commit_patches": [] } ], - "references_v2": [{"reference_id": "", "reference_type": "", "url": "https://cve.tuxcare.com/"}], + "references_v2": [], "patches": [], - "severities": [{"system": "generic_textual", "value": "HIGH (7.4)", "scoring_elements": "score=7.4,severity=HIGH"}], + "severities": [{"system": "generic_textual", "value": "7.4", "scoring_elements": "HIGH"}], "date_published": "2025-12-23T08:55:06.706873+00:00", "weaknesses": [], - "url": "https://cve.tuxcare.com/" + "url": "https://cve.tuxcare.com/els/cve/CVE-2025-21587" } ] From 83bb44d6cb163d6e0a91c43cbff4127bf0ddadb4 Mon Sep 17 00:00:00 2001 From: Sampurna Pyne Date: Sat, 10 Jan 2026 05:10:57 +0530 Subject: [PATCH 3/4] Refactor to PURL qualifier Signed-off-by: Sampurna Pyne --- .../v2_importers/tuxcare_importer.py | 34 +++++++++++++++++-- .../tests/test_data/tuxcare/expected.json | 10 +++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py index e93e448d0..dee36f461 100644 --- a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py +++ b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py @@ -1,7 +1,6 @@ import json import logging -from typing import Iterable -from typing import Mapping +from typing import Iterable, Mapping from dateutil.parser import parse from packageurl import PackageURL @@ -39,6 +38,35 @@ def fetch(self) -> Iterable[Mapping]: def advisories_count(self) -> int: return len(self.response) + def _create_purl(self, project_name: str, os_name: str) -> PackageURL: + os_mapping = { + "ubuntu": ("deb", "ubuntu"), + "debian": ("deb", "debian"), + "centos": ("rpm", "centos"), + "almalinux": ("rpm", "almalinux"), + "rhel": ("rpm", "redhat"), + "red hat": ("rpm", "redhat"), + "oracle": ("rpm", "oracle"), + "cloudlinux": ("rpm", "cloudlinux"), + "alpine": ("apk", "alpine"), + } + + qualifiers = {} + if os_name: + qualifiers["os"] = os_name + + if not os_name: + return PackageURL(type="generic", name=project_name) + + os_lower = os_name.lower() + for keyword, (pkg_type, namespace) in os_mapping.items(): + if keyword in os_lower: + return PackageURL( + type=pkg_type, namespace=namespace, name=project_name, qualifiers=qualifiers + ) + + return PackageURL(type="generic", name=project_name, qualifiers=qualifiers) + def collect_advisories(self) -> Iterable[AdvisoryData]: for record in self.response: cve_id = record.get("cve", "").strip() @@ -62,7 +90,7 @@ def collect_advisories(self) -> Iterable[AdvisoryData]: affected_packages = [] if project_name: - purl = PackageURL(type="generic", name=project_name) + purl = self._create_purl(project_name, os_name) affected_version_range = None if version: diff --git a/vulnerabilities/tests/test_data/tuxcare/expected.json b/vulnerabilities/tests/test_data/tuxcare/expected.json index 96287039a..9ab7ebdd3 100644 --- a/vulnerabilities/tests/test_data/tuxcare/expected.json +++ b/vulnerabilities/tests/test_data/tuxcare/expected.json @@ -5,7 +5,7 @@ "summary": "TuxCare advisory for CVE-2023-52922 in squid on CloudLinux 7 ELS", "affected_packages": [ { - "package": {"type": "generic", "namespace": "", "name": "squid", "version": "", "qualifiers": "", "subpath": ""}, + "package": {"type": "rpm", "namespace": "cloudlinux", "name": "squid", "version": "", "qualifiers": "os=CloudLinux%207%20ELS", "subpath": ""}, "affected_version_range": "vers:generic/3.5.20", "fixed_version_range": null, "introduced_by_commit_patches": [], @@ -25,7 +25,7 @@ "summary": "TuxCare advisory for CVE-2023-52922 in squid on Oracle Linux 7 ELS", "affected_packages": [ { - "package": {"type": "generic", "namespace": "", "name": "squid", "version": "", "qualifiers": "", "subpath": ""}, + "package": {"type": "rpm", "namespace": "oracle", "name": "squid", "version": "", "qualifiers": "os=Oracle%20Linux%207%20ELS", "subpath": ""}, "affected_version_range": "vers:generic/3.5.20", "fixed_version_range": null, "introduced_by_commit_patches": [], @@ -45,7 +45,7 @@ "summary": "TuxCare advisory for CVE-2023-48161 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { - "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "package": {"type": "rpm", "namespace": "redhat", "name": "java-11-openjdk", "version": "", "qualifiers": "os=RHEL%207%20ELS", "subpath": ""}, "affected_version_range": "vers:generic/11.0.23", "fixed_version_range": null, "introduced_by_commit_patches": [], @@ -65,7 +65,7 @@ "summary": "TuxCare advisory for CVE-2024-21147 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { - "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "package": {"type": "rpm", "namespace": "redhat", "name": "java-11-openjdk", "version": "", "qualifiers": "os=RHEL%207%20ELS", "subpath": ""}, "affected_version_range": "vers:generic/11.0.23", "fixed_version_range": null, "introduced_by_commit_patches": [], @@ -85,7 +85,7 @@ "summary": "TuxCare advisory for CVE-2025-21587 in java-11-openjdk on RHEL 7 ELS", "affected_packages": [ { - "package": {"type": "generic", "namespace": "", "name": "java-11-openjdk", "version": "", "qualifiers": "", "subpath": ""}, + "package": {"type": "rpm", "namespace": "redhat", "name": "java-11-openjdk", "version": "", "qualifiers": "os=RHEL%207%20ELS", "subpath": ""}, "affected_version_range": "vers:generic/11.0.23", "fixed_version_range": null, "introduced_by_commit_patches": [], From 0a8456a0cec9f0581aa64e1b9dd21b3b7cb0328b Mon Sep 17 00:00:00 2001 From: Sampurna Pyne Date: Sat, 10 Jan 2026 18:44:30 +0530 Subject: [PATCH 4/4] Codestyle fix Signed-off-by: Sampurna Pyne --- vulnerabilities/pipelines/v2_importers/tuxcare_importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py index dee36f461..6ecd08997 100644 --- a/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py +++ b/vulnerabilities/pipelines/v2_importers/tuxcare_importer.py @@ -1,6 +1,7 @@ import json import logging -from typing import Iterable, Mapping +from typing import Iterable +from typing import Mapping from dateutil.parser import parse from packageurl import PackageURL