Skip to content

Commit 2c0b5eb

Browse files
committed
Compute valid CVSS scores from vectors in GitLab importer
Resolves #2330: - Use Cvssv2ScoringSystem and Cvssv3ScoringSystem to compute numeric base scores from GitLab cvss_v2 and cvss_v3 vectors - Support CVSS 3.0 and CVSS 3.1 scoring systems - Handle malformed vectors gracefully via CVSS2MalformedError and CVSS3MalformedError without interrupting importer execution - Add regression tests verifying score computation and malformed vector handling
1 parent c3adaf0 commit 2c0b5eb

2 files changed

Lines changed: 145 additions & 3 deletions

File tree

vulnerabilities/pipelines/v2_importers/gitlab_importer.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import pytz
1818
import saneyaml
19+
from cvss.exceptions import CVSS2MalformedError
20+
from cvss.exceptions import CVSS3MalformedError
1921
from dateutil import parser as dateparser
2022
from fetchcode.vcs import fetch_via_vcs
2123
from packageurl import PackageURL
@@ -302,23 +304,41 @@ def parse_gitlab_advisory(
302304
cvss_v3 = gitlab_advisory.get("cvss_v3")
303305
severities = []
304306
if cvss_v2:
307+
cvss_v2_clean = cvss_v2.strip()
308+
value = None
309+
try:
310+
value = SCORING_SYSTEMS["cvssv2"].compute(cvss_v2_clean)
311+
except CVSS2MalformedError as e:
312+
logger(
313+
f"parse_gitlab_advisory: Invalid CVSSv2 vector {cvss_v2!r}: {e}",
314+
level=logging.ERROR,
315+
)
305316
severities.append(
306317
VulnerabilitySeverity(
307318
system=SCORING_SYSTEMS["cvssv2"],
308319
scoring_elements=cvss_v2,
309-
value=None,
320+
value=value,
310321
url=advisory_url,
311322
)
312323
)
313324
if cvss_v3:
325+
cvss_v3_clean = cvss_v3.strip()
314326
scoring_system = SCORING_SYSTEMS["cvssv3"]
315-
if cvss_v3.startswith("CVSS:3.1/"):
327+
if cvss_v3_clean.startswith("CVSS:3.1/"):
316328
scoring_system = SCORING_SYSTEMS["cvssv3.1"]
329+
value = None
330+
try:
331+
value = scoring_system.compute(cvss_v3_clean)
332+
except CVSS3MalformedError as e:
333+
logger(
334+
f"parse_gitlab_advisory: Invalid CVSSv3 vector {cvss_v3!r}: {e}",
335+
level=logging.ERROR,
336+
)
317337
severities.append(
318338
VulnerabilitySeverity(
319339
system=scoring_system,
320340
scoring_elements=cvss_v3,
321-
value=None,
341+
value=value,
322342
url=advisory_url,
323343
)
324344
)

vulnerabilities/tests/pipelines/v2_importers/test_gitlab_importer_v2.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,125 @@ def mock_get_purl(package_slug, purl_type_by_gitlab_scheme, logger):
206206
assert isinstance(result.date_published, datetime)
207207
assert result.date_published.year == 2018
208208
assert result.affected_packages == [] # Because get_purl was mocked to return None
209+
210+
211+
def test_parse_gitlab_advisory_computes_cvss_scores(tmp_path):
212+
content = {
213+
"identifier": "CVE-2019-1010083",
214+
"package_slug": "pypi/Flask",
215+
"title": "Denial of service",
216+
"description": "Denial of Service due to unexpected memory usage in the Pallets Project Flask",
217+
"pubdate": "2019-07-17",
218+
"affected_range": "<1.0",
219+
"fixed_versions": ["1.0"],
220+
"urls": ["https://nvd.nist.gov/vuln/detail/CVE-2019-1010083"],
221+
"cvss_v2": "AV:N/AC:L/Au:N/C:N/I:N/A:P",
222+
"cvss_v3": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
223+
"cwe_ids": ["CWE-1035", "CWE-937"],
224+
"identifiers": ["CVE-2019-1010083"],
225+
}
226+
advisory_path = tmp_path / "CVE-2019-1010083.yaml"
227+
advisory_path.write_text(saneyaml.dump(content))
228+
229+
dummy_logger = lambda *args, **kwargs: None
230+
result = parse_gitlab_advisory(
231+
file=advisory_path,
232+
base_path=advisory_path.parent,
233+
gitlab_scheme_by_purl_type={"pypi": "pypi"},
234+
purl_type_by_gitlab_scheme={"pypi": "pypi"},
235+
logger=dummy_logger,
236+
)
237+
238+
assert isinstance(result, AdvisoryDataV2)
239+
assert len(result.severities) == 2
240+
241+
cvss_v2_sev = result.severities[0]
242+
assert cvss_v2_sev.system.identifier == "cvssv2"
243+
assert cvss_v2_sev.scoring_elements == "AV:N/AC:L/Au:N/C:N/I:N/A:P"
244+
assert cvss_v2_sev.value == "5.0"
245+
246+
cvss_v3_sev = result.severities[1]
247+
assert cvss_v3_sev.system.identifier == "cvssv3"
248+
assert cvss_v3_sev.scoring_elements == "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
249+
assert cvss_v3_sev.value == "7.5"
250+
251+
252+
def test_parse_gitlab_advisory_cvss_v31_scoring_system(tmp_path):
253+
content = {
254+
"identifier": "CVE-2023-0001",
255+
"package_slug": "pypi/django",
256+
"title": "Vulnerability with CVSS 3.1",
257+
"description": "Test CVSS 3.1 scoring system selection and computation",
258+
"pubdate": "2023-01-01",
259+
"affected_range": "<4.0.0",
260+
"fixed_versions": ["4.0.0"],
261+
"urls": ["https://example.com/advisory"],
262+
"cvss_v3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
263+
"cwe_ids": ["CWE-79"],
264+
"identifiers": ["CVE-2023-0001"],
265+
}
266+
advisory_path = tmp_path / "CVE-2023-0001.yaml"
267+
advisory_path.write_text(saneyaml.dump(content))
268+
269+
dummy_logger = lambda *args, **kwargs: None
270+
result = parse_gitlab_advisory(
271+
file=advisory_path,
272+
base_path=advisory_path.parent,
273+
gitlab_scheme_by_purl_type={"pypi": "pypi"},
274+
purl_type_by_gitlab_scheme={"pypi": "pypi"},
275+
logger=dummy_logger,
276+
)
277+
278+
assert isinstance(result, AdvisoryDataV2)
279+
assert len(result.severities) == 1
280+
cvss_v31_sev = result.severities[0]
281+
assert cvss_v31_sev.system.identifier == "cvssv3.1"
282+
assert cvss_v31_sev.scoring_elements == "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"
283+
assert cvss_v31_sev.value == "9.8"
284+
285+
286+
def test_parse_gitlab_advisory_malformed_cvss_vectors(tmp_path):
287+
content = {
288+
"identifier": "CVE-2023-0002",
289+
"package_slug": "pypi/django",
290+
"title": "Vulnerability with malformed vectors",
291+
"description": "Test error handling for malformed CVSS vectors",
292+
"pubdate": "2023-01-01",
293+
"affected_range": "<4.0.0",
294+
"fixed_versions": ["4.0.0"],
295+
"urls": ["https://example.com/advisory"],
296+
"cvss_v2": "MALFORMED_CVSS2_VECTOR",
297+
"cvss_v3": "MALFORMED_CVSS3_VECTOR",
298+
"cwe_ids": ["CWE-79"],
299+
"identifiers": ["CVE-2023-0002"],
300+
}
301+
advisory_path = tmp_path / "CVE-2023-0002.yaml"
302+
advisory_path.write_text(saneyaml.dump(content))
303+
304+
logged_errors = []
305+
306+
def recording_logger(msg, level=None):
307+
logged_errors.append((msg, level))
308+
309+
result = parse_gitlab_advisory(
310+
file=advisory_path,
311+
base_path=advisory_path.parent,
312+
gitlab_scheme_by_purl_type={"pypi": "pypi"},
313+
purl_type_by_gitlab_scheme={"pypi": "pypi"},
314+
logger=recording_logger,
315+
)
316+
317+
assert isinstance(result, AdvisoryDataV2)
318+
assert len(result.severities) == 2
319+
320+
assert result.severities[0].system.identifier == "cvssv2"
321+
assert result.severities[0].scoring_elements == "MALFORMED_CVSS2_VECTOR"
322+
assert result.severities[0].value == ""
323+
324+
assert result.severities[1].system.identifier == "cvssv3"
325+
assert result.severities[1].scoring_elements == "MALFORMED_CVSS3_VECTOR"
326+
assert result.severities[1].value == ""
327+
328+
assert len(logged_errors) == 2
329+
assert any("Invalid CVSSv2 vector" in msg for msg, _ in logged_errors)
330+
assert any("Invalid CVSSv3 vector" in msg for msg, _ in logged_errors)

0 commit comments

Comments
 (0)