Skip to content

fix: VulnerabilityScoreSource.get_from_vector() for CVSS_V3_1 and CVSS_V4 #824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions cyclonedx/model/vulnerability.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,14 +611,17 @@ def get_from_vector(vector: str) -> 'VulnerabilityScoreSource':
Always returns an instance of `VulnerabilityScoreSource`. `VulnerabilityScoreSource.OTHER` is
returned if the scheme is not obvious or known to us.
"""
if vector.startswith('CVSS:4.'):
return VulnerabilityScoreSource.CVSS_V4
if vector.startswith('CVSS:3.'):
if vector.startswith('CVSS:3.1'):
return VulnerabilityScoreSource.CVSS_V3_1
return VulnerabilityScoreSource.CVSS_V3
elif vector.startswith('CVSS:2.'):
if vector.startswith('CVSS:2.'):
return VulnerabilityScoreSource.CVSS_V2
elif vector.startswith('OWASP'):
if vector.startswith('OWASP'):
return VulnerabilityScoreSource.OWASP
else:
return VulnerabilityScoreSource.OTHER
return VulnerabilityScoreSource.OTHER

def get_localised_vector(self, vector: str) -> str:
"""
Expand All @@ -630,15 +633,16 @@ def get_localised_vector(self, vector: str) -> str:
Returns:
The vector without any scheme prefix as a `str`.
"""
if self == VulnerabilityScoreSource.CVSS_V3 and vector.startswith('CVSS:3.'):
return re.sub('^CVSS:3\\.\\d/?', '', vector)

if self == VulnerabilityScoreSource.CVSS_V2 and vector.startswith('CVSS:2.'):
return re.sub('^CVSS:2\\.\\d/?', '', vector)

if self == VulnerabilityScoreSource.OWASP and vector.startswith('OWASP'):
return re.sub('^OWASP/?', '', vector)

if self is VulnerabilityScoreSource.CVSS_V4 and vector.startswith('CVSS:4.'):
return re.sub(r'^CVSS:4\.\d/?', '', vector)
if (
self in (VulnerabilityScoreSource.CVSS_V3_1, VulnerabilityScoreSource.CVSS_V3)
) and vector.startswith('CVSS:3.'):
return re.sub(r'^CVSS:3\.\d/?', '', vector)
if self is VulnerabilityScoreSource.CVSS_V2 and vector.startswith('CVSS:2.'):
return re.sub(r'^CVSS:2\.\d/?', '', vector)
if self is VulnerabilityScoreSource.OWASP and vector.startswith('OWASP'):
return re.sub(r'^OWASP/?', '', vector)
return vector

def get_value_pre_1_4(self) -> str:
Expand All @@ -649,7 +653,7 @@ def get_value_pre_1_4(self) -> str:
Returns:
`str`
"""
if self == VulnerabilityScoreSource.OWASP:
if self is VulnerabilityScoreSource.OWASP:
return 'OWASP Risk'
return self.value # type:ignore[no-any-return]

Expand Down
130 changes: 104 additions & 26 deletions tests/test_model_vulnerability.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
from tests import reorder


class TestModelVulnerability(TestCase):
class TestModelVulnerabilitySeverity(TestCase):

def test_v_severity_from_cvss_scores_single_critical(self) -> None:
self.assertEqual(
Expand Down Expand Up @@ -85,87 +85,165 @@ def test_v_severity_from_cvss_scores_multiple_high(self) -> None:
VulnerabilitySeverity.HIGH
)


class TestModelVulnerabilityScoreSource(TestCase):

def test_v_source_parse_other(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector('loremIpsum'),
VulnerabilityScoreSource.OTHER
)

def test_v_source_parse_cvss4_0(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector(
'CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:U'),
VulnerabilityScoreSource.CVSS_V4
)

def test_v_source_parse_cvss3_1(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector('CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.get_from_vector(
'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H'),
VulnerabilityScoreSource.CVSS_V3_1
)

def test_v_source_parse_cvss3_0(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector(
'CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.CVSS_V3
)

def test_v_source_parse_cvss2_1(self) -> None:
def test_v_source_parse_cvss2_0(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector('CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'),
VulnerabilityScoreSource.get_from_vector(
'CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'),
VulnerabilityScoreSource.CVSS_V2
)

def test_v_source_parse_owasp_1(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.get_from_vector('OWASP/K9:M1:O0:Z2/D1:X1:W1:L3/C2:I1:A1:T1/F1:R1:S2:P3/50'),
VulnerabilityScoreSource.get_from_vector(
'OWASP/K9:M1:O0:Z2/D1:X1:W1:L3/C2:I1:A1:T1/F1:R1:S2:P3/50'),
VulnerabilityScoreSource.OWASP
)

def test_v_source_get_localised_vector_cvss3_1(self) -> None:
def test_v_source_get_localised_vector_cvss4_slash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V4.get_localised_vector(
'CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'),
'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'
)

def test_v_source_get_localised_vector_cvss4_noslash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V4.get_localised_vector(
'CVSS:4.0AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'),
'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'
)

def test_v_source_get_localised_vector_cvss4_none(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V4.get_localised_vector(
'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'),
'AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N'
)

def test_v_source_get_localised_vector_cvss3_1_slash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(
'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H'),
'AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H'
)

def test_v_source_get_localised_vector_cvss3_1_noslash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3_1.get_localised_vector(
'CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss3_1_none(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3_1.get_localised_vector(
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss3_slash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(
vector='CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
),
'CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss3_2(self) -> None:
def test_v_source_get_localised_vector_cvss3_noslash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(vector='CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(
'CVSS:3.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss3_3(self) -> None:
def test_v_source_get_localised_vector_cvss3_none(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.CVSS_V3.get_localised_vector(
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss2_1(self) -> None:
def test_v_source_get_localised_vector_cvss2_slash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V2.get_localised_vector(
vector='CVSS:2.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'CVSS:2.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss2_2(self) -> None:
def test_v_source_get_localised_vector_cvss2_noslash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V2.get_localised_vector(vector='CVSS:2.1AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.CVSS_V2.get_localised_vector(
'CVSS:2.0AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_cvss2_3(self) -> None:
def test_v_source_get_localised_vector_cvss2_none(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.CVSS_V2.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.CVSS_V2.get_localised_vector(
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_owasp_1(self) -> None:
def test_v_source_get_localised_vector_owasp_slash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.OWASP.get_localised_vector(vector='OWASP/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.OWASP.get_localised_vector(
'OWASP/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_owasp_2(self) -> None:
def test_v_source_get_localised_vector_owasp_noslash(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.OWASP.get_localised_vector(vector='OWASPAV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.OWASP.get_localised_vector(
'OWASPAV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_owasp_3(self) -> None:
def test_v_source_get_localised_vector_owasp_none(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.OWASP.get_localised_vector(vector='AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
VulnerabilityScoreSource.OWASP.get_localised_vector(
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
'AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'
)

def test_v_source_get_localised_vector_other_2(self) -> None:
def test_v_source_get_localised_vector_other(self) -> None:
self.assertEqual(
VulnerabilityScoreSource.OTHER.get_localised_vector(vector='SOMETHING_OR_OTHER'),
VulnerabilityScoreSource.OTHER.get_localised_vector(
'SOMETHING_OR_OTHER'),
'SOMETHING_OR_OTHER'
)


class TestModelVulnerability(TestCase):

def test_empty_vulnerability(self) -> None:
v = Vulnerability()
self.assertIsNone(v.bom_ref.value)
Expand Down