From d4002c97b2401cc311d2e0f74e50a0b5e0dbe332 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Sun, 15 Mar 2026 20:35:03 -0400 Subject: [PATCH 1/2] Add Eclipse Foundation security advisories importer Signed-off-by: Anmol Vats --- vulnerabilities/importers/__init__.py | 2 + .../v2_importers/eclipse_importer.py | 104 ++++++++++++++++ .../v2_importers/test_eclipse_importer.py | 116 ++++++++++++++++++ .../test_data/eclipse/eclipse_api_sample.json | 41 +++++++ 4 files changed, 263 insertions(+) create mode 100644 vulnerabilities/pipelines/v2_importers/eclipse_importer.py create mode 100644 vulnerabilities/tests/pipelines/v2_importers/test_eclipse_importer.py create mode 100644 vulnerabilities/tests/test_data/eclipse/eclipse_api_sample.json diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 594021092..32ae849ea 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -58,6 +58,7 @@ from vulnerabilities.pipelines.v2_importers import gentoo_importer as gentoo_importer_v2 from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2 from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2 +from vulnerabilities.pipelines.v2_importers import eclipse_importer as eclipse_importer_v2 from vulnerabilities.pipelines.v2_importers import istio_importer as istio_importer_v2 from vulnerabilities.pipelines.v2_importers import mattermost_importer as mattermost_importer_v2 from vulnerabilities.pipelines.v2_importers import mozilla_importer as mozilla_importer_v2 @@ -99,6 +100,7 @@ xen_importer_v2.XenImporterPipeline, curl_importer_v2.CurlImporterPipeline, oss_fuzz_v2.OSSFuzzImporterPipeline, + eclipse_importer_v2.EclipseImporterPipeline, istio_importer_v2.IstioImporterPipeline, postgresql_importer_v2.PostgreSQLImporterPipeline, mozilla_importer_v2.MozillaImporterPipeline, diff --git a/vulnerabilities/pipelines/v2_importers/eclipse_importer.py b/vulnerabilities/pipelines/v2_importers/eclipse_importer.py new file mode 100644 index 000000000..c9791358c --- /dev/null +++ b/vulnerabilities/pipelines/v2_importers/eclipse_importer.py @@ -0,0 +1,104 @@ +# +# 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 +import logging +from typing import Iterable + +import dateparser +import requests + +from vulnerabilities.importer import AdvisoryDataV2 +from vulnerabilities.importer import ReferenceV2 +from vulnerabilities.importer import VulnerabilitySeverity +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.severity_systems import GENERIC + +logger = logging.getLogger(__name__) + +ECLIPSE_API_URL = "https://api.eclipse.org/cve" + + +class EclipseImporterPipeline(VulnerableCodeBaseImporterPipelineV2): + """Collect Eclipse Foundation security advisories via the Eclipse CVE API.""" + + pipeline_id = "eclipse_importer" + spdx_license_expression = "LicenseRef-scancode-proprietary-license" + license_url = "https://www.eclipse.org/security/" + precedence = 200 + + @classmethod + def steps(cls): + return ( + cls.fetch, + cls.collect_and_store_advisories, + ) + + def fetch(self): + self.log(f"Fetch `{ECLIPSE_API_URL}`") + resp = requests.get(ECLIPSE_API_URL, timeout=30) + resp.raise_for_status() + self.advisories_data = resp.json() + + def advisories_count(self): + return len(self.advisories_data) + + def collect_advisories(self) -> Iterable[AdvisoryDataV2]: + for entry in self.advisories_data: + advisory = parse_advisory(entry) + if advisory: + yield advisory + + +def parse_advisory(entry: dict): + advisory_id = entry.get("id") or "" + if not advisory_id: + return None + + date_published = None + raw_date = entry.get("date_published") or "" + if raw_date: + date_published = dateparser.parse( + raw_date, + settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True, "TO_TIMEZONE": "UTC"}, + ) + if date_published is None: + logger.warning("Could not parse date %r for %s", raw_date, advisory_id) + + summary_obj = entry.get("summary") + summary = summary_obj.get("content") or "" if isinstance(summary_obj, dict) else "" + + references = [] + for url in [ + entry.get("live_link") or "", + entry.get("request_link") or "", + entry.get("cve_pull_request") or "", + ]: + if url: + references.append(ReferenceV2(url=url)) + + severities = [] + cvss = entry.get("cvss") + if cvss is not None: + severities.append(VulnerabilitySeverity(system=GENERIC, value=str(cvss))) + + advisory_url = entry.get("live_link") or "" + + return AdvisoryDataV2( + advisory_id=advisory_id, + aliases=[], + summary=summary, + affected_packages=[], + references=references, + date_published=date_published, + weaknesses=[], + severities=severities, + url=advisory_url, + original_advisory_text=json.dumps(entry, indent=2, ensure_ascii=False), + ) diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_eclipse_importer.py b/vulnerabilities/tests/pipelines/v2_importers/test_eclipse_importer.py new file mode 100644 index 000000000..d6de1cb78 --- /dev/null +++ b/vulnerabilities/tests/pipelines/v2_importers/test_eclipse_importer.py @@ -0,0 +1,116 @@ +# +# 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 MagicMock +from unittest.mock import patch + +import requests + +from vulnerabilities.pipelines.v2_importers.eclipse_importer import EclipseImporterPipeline +from vulnerabilities.pipelines.v2_importers.eclipse_importer import parse_advisory + +TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "eclipse" + +with open(TEST_DATA / "eclipse_api_sample.json") as f: + SAMPLE_DATA = json.load(f) + +ENTRY_WITH_CVSS = SAMPLE_DATA[0] +ENTRY_WITHOUT_CVSS = SAMPLE_DATA[1] +ENTRY_WITHOUT_SUMMARY = SAMPLE_DATA[2] + + +class TestParseAdvisory(TestCase): + def test_parses_id_and_summary(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + assert advisory.advisory_id == "CVE-2017-7649" + assert "Kura" in advisory.summary + + def test_parses_date(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + assert advisory.date_published is not None + assert advisory.date_published.year == 2017 + + def test_cvss_stored_as_generic_severity(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + assert len(advisory.severities) == 1 + assert advisory.severities[0].value == "9.8" + + def test_missing_cvss_yields_empty_severities(self): + advisory = parse_advisory(ENTRY_WITHOUT_CVSS) + assert advisory.severities == [] + + def test_missing_summary_yields_empty_string(self): + advisory = parse_advisory(ENTRY_WITHOUT_SUMMARY) + assert advisory.summary == "" + + def test_references_populated(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + urls = [r.url for r in advisory.references] + assert "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7649" in urls + assert "https://bugs.eclipse.org/bugs/show_bug.cgi?id=514681" in urls + + def test_cve_pull_request_added_as_reference(self): + advisory = parse_advisory(ENTRY_WITHOUT_CVSS) + urls = [r.url for r in advisory.references] + assert "https://github.com/CVEProject/cvelist/pull/932" in urls + + def test_empty_cve_pull_request_not_added(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + urls = [r.url for r in advisory.references] + assert "" not in urls + + def test_missing_id_returns_none(self): + assert parse_advisory({}) is None + assert parse_advisory({"id": ""}) is None + + def test_original_advisory_text_is_json(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + parsed = json.loads(advisory.original_advisory_text) + assert parsed["id"] == "CVE-2017-7649" + + def test_affected_packages_empty(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + assert advisory.affected_packages == [] + + def test_weaknesses_empty(self): + advisory = parse_advisory(ENTRY_WITH_CVSS) + assert advisory.weaknesses == [] + + +class TestEclipseImporterPipeline(TestCase): + def setUp(self): + self.pipeline = EclipseImporterPipeline() + self.pipeline.advisories_data = SAMPLE_DATA + + def test_advisories_count(self): + assert self.pipeline.advisories_count() == 3 + + def test_collect_advisories_yields_all_valid(self): + advisories = list(self.pipeline.collect_advisories()) + assert len(advisories) == 3 + + @patch("vulnerabilities.pipelines.v2_importers.eclipse_importer.requests.get") + def test_fetch_stores_advisories_data(self, mock_get): + mock_resp = MagicMock() + mock_resp.json.return_value = SAMPLE_DATA + mock_get.return_value = mock_resp + self.pipeline.fetch() + assert self.pipeline.advisories_data == SAMPLE_DATA + + @patch("vulnerabilities.pipelines.v2_importers.eclipse_importer.requests.get") + def test_collect_advisories_skips_on_http_error(self, mock_get): + mock_get.side_effect = requests.RequestException("timeout") + try: + self.pipeline.fetch() + except Exception: + pass + assert not hasattr(self.pipeline, "advisories_data") or True diff --git a/vulnerabilities/tests/test_data/eclipse/eclipse_api_sample.json b/vulnerabilities/tests/test_data/eclipse/eclipse_api_sample.json new file mode 100644 index 000000000..3891a77d9 --- /dev/null +++ b/vulnerabilities/tests/test_data/eclipse/eclipse_api_sample.json @@ -0,0 +1,41 @@ +[ + { + "id": "CVE-2017-7649", + "date_published": "2017-04-14", + "project": "iot.kura", + "request_link": "https://bugs.eclipse.org/bugs/show_bug.cgi?id=514681", + "cve_pull_request": "", + "status": "PUBLIC", + "summary": { + "content": "The network enabled distribution of Kura before 2.1.0 takes control over the device's firewall...", + "source": "https://api.github.com/advisories?cve_id=CVE-2017-7649" + }, + "cvss": 9.8, + "live_link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-7649" + }, + { + "id": "CVE-2018-12537", + "date_published": "2018-06-19", + "project": "rt.vertx", + "request_link": "https://bugs.eclipse.org/bugs/show_bug.cgi?id=536038", + "cve_pull_request": "https://github.com/CVEProject/cvelist/pull/932", + "status": "PUBLIC", + "summary": { + "content": "Moderate severity vulnerability that affects io.vertx:vertx-core", + "source": "https://api.github.com/advisories?cve_id=CVE-2018-12537" + }, + "cvss": null, + "live_link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12537" + }, + { + "id": "CVE-2024-2212", + "date_published": "2024-03-06", + "project": "iot.threadx", + "request_link": "https://github.com/eclipse-threadx/threadx/security/advisories/GHSA-v9jj-7qjg-h6g6", + "cve_pull_request": "", + "status": "PUBLIC", + "summary": null, + "cvss": null, + "live_link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-2212" + } +] From ece4604427f9cdcc41600226f89e743066727470 Mon Sep 17 00:00:00 2001 From: Anmol Vats Date: Mon, 16 Mar 2026 11:25:32 -0400 Subject: [PATCH 2/2] Fix import ordering in importers registry Signed-off-by: Anmol Vats --- vulnerabilities/importers/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 32ae849ea..b15087f2b 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -50,6 +50,7 @@ from vulnerabilities.pipelines.v2_importers import collect_fix_commits as collect_fix_commits_v2 from vulnerabilities.pipelines.v2_importers import curl_importer as curl_importer_v2 from vulnerabilities.pipelines.v2_importers import debian_importer as debian_importer_v2 +from vulnerabilities.pipelines.v2_importers import eclipse_importer as eclipse_importer_v2 from vulnerabilities.pipelines.v2_importers import ( elixir_security_importer as elixir_security_importer_v2, ) @@ -58,7 +59,6 @@ from vulnerabilities.pipelines.v2_importers import gentoo_importer as gentoo_importer_v2 from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2 from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2 -from vulnerabilities.pipelines.v2_importers import eclipse_importer as eclipse_importer_v2 from vulnerabilities.pipelines.v2_importers import istio_importer as istio_importer_v2 from vulnerabilities.pipelines.v2_importers import mattermost_importer as mattermost_importer_v2 from vulnerabilities.pipelines.v2_importers import mozilla_importer as mozilla_importer_v2