Skip to content

Commit f228798

Browse files
committed
Modify Curl importer to support package-first mode #1918
* Update Curl importer to filter and process advisories relevant to the purl passed in the constructor * Update Curl importer tests to include testing the package-first mode Signed-off-by: Michael Ehab Mikhail <[email protected]>
1 parent a05b65e commit f228798

File tree

2 files changed

+98
-3
lines changed

2 files changed

+98
-3
lines changed

vulnerabilities/importers/curl.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ class CurlImporter(Importer):
3939
importer_name = "Curl Importer"
4040
api_url = "https://curl.se/docs/vuln.json"
4141

42+
def __init__(self, purl=None, *args, **kwargs):
43+
super().__init__(*args, **kwargs)
44+
self.purl = purl
45+
if self.purl:
46+
if self.purl.type != "generic" or self.purl.name != "curl":
47+
print(
48+
f"Warning: This importer handles curl package vulnerabilities. Current PURL: {self.purl!s}"
49+
)
50+
4251
def fetch(self) -> Iterable[Mapping]:
4352
response = fetch_response(self.api_url)
4453
return response.json()
@@ -48,11 +57,42 @@ def advisory_data(self) -> Iterable[AdvisoryData]:
4857
for data in raw_data:
4958
cve_id = data.get("aliases") or []
5059
cve_id = cve_id[0] if len(cve_id) > 0 else None
51-
if not cve_id.startswith("CVE"):
52-
package = data.get("database_specific").get("package")
60+
if not cve_id or not cve_id.startswith("CVE"):
61+
package = data.get("database_specific", {}).get("package", "")
5362
logger.error(f"Invalid CVE ID: {cve_id} in package {package}")
5463
continue
55-
yield parse_advisory_data(data)
64+
65+
advisory = parse_advisory_data(data)
66+
67+
if self.purl and not self._advisory_affects_purl(advisory):
68+
continue
69+
70+
yield advisory
71+
72+
def _advisory_affects_purl(self, advisory: AdvisoryData) -> bool:
73+
if not self.purl:
74+
return True
75+
76+
if self.purl.type != "generic" or self.purl.name != "curl":
77+
return False
78+
79+
for affected_package in advisory.affected_packages:
80+
if affected_package.package.name != "curl":
81+
continue
82+
83+
if self.purl.version and affected_package.affected_version_range:
84+
try:
85+
purl_version = SemverVersion(self.purl.version)
86+
87+
if purl_version not in affected_package.affected_version_range:
88+
continue
89+
except Exception as e:
90+
logger.error(f"Error checking version {self.purl.version}: {e}")
91+
continue
92+
93+
return True
94+
95+
return False
5696

5797

5898
def parse_advisory_data(raw_data) -> AdvisoryData:

vulnerabilities/tests/test_curl.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
import json
1011
import os
1112
from unittest import TestCase
1213
from unittest.mock import patch
1314

15+
import pytest
16+
from packageurl import PackageURL
17+
from univers.versions import SemverVersion
18+
19+
from vulnerabilities.importers.curl import CurlImporter
1420
from vulnerabilities.importers.curl import get_cwe_from_curl_advisory
1521
from vulnerabilities.importers.curl import parse_advisory_data
1622
from vulnerabilities.tests import util_tests
@@ -71,3 +77,52 @@ def test_get_cwe_from_curl_advisory(self):
7177
for advisory in mock_advisory:
7278
mock_cwe_list.extend(get_cwe_from_curl_advisory(advisory))
7379
assert mock_cwe_list == [311]
80+
81+
82+
@pytest.fixture
83+
def mock_curl_api(monkeypatch):
84+
test_files = [
85+
"curl_advisory_mock1.json",
86+
"curl_advisory_mock2.json",
87+
"curl_advisory_mock3.json",
88+
]
89+
90+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
91+
TEST_DATA = os.path.join(BASE_DIR, "test_data/curl")
92+
data = []
93+
for fname in test_files:
94+
with open(os.path.join(TEST_DATA, fname)) as f:
95+
data.append(json.load(f))
96+
97+
def mock_fetch(self):
98+
return data
99+
100+
monkeypatch.setattr(CurlImporter, "fetch", mock_fetch)
101+
102+
103+
def test_curl_importer_package_first(monkeypatch, mock_curl_api):
104+
purl = PackageURL(type="generic", namespace="curl.se", name="curl")
105+
importer = CurlImporter(purl=purl)
106+
advisories = list(importer.advisory_data())
107+
assert len(advisories) == 3
108+
for adv in advisories:
109+
assert any(ap.package.name == "curl" for ap in adv.affected_packages)
110+
111+
112+
def test_curl_importer_package_first_version(monkeypatch, mock_curl_api):
113+
purl = PackageURL(type="generic", namespace="curl.se", name="curl", version="8.6.0")
114+
importer = CurlImporter(purl=purl)
115+
advisories = list(importer.advisory_data())
116+
117+
assert len(advisories) == 1
118+
assert advisories[0].aliases[0] == "CVE-2024-2379"
119+
120+
for ap in advisories[0].affected_packages:
121+
assert ap.affected_version_range.contains(SemverVersion("8.6.0"))
122+
123+
124+
def test_curl_importer_package_first_version_not_affected(monkeypatch, mock_curl_api):
125+
purl = PackageURL(type="generic", namespace="curl.se", name="curl", version="9.9.9")
126+
importer = CurlImporter(purl=purl)
127+
advisories = list(importer.advisory_data())
128+
assert advisories == []

0 commit comments

Comments
 (0)