@@ -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