Skip to content

Commit 6a86ec2

Browse files
committed
feat: helper methods for deriving Severity and SourceType
Signed-off-by: Paul Horton <[email protected]>
1 parent ba4f285 commit 6a86ec2

File tree

2 files changed

+109
-2
lines changed

2 files changed

+109
-2
lines changed

cyclonedx/model/vulnerability.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,29 @@ class VulnerabilitySourceType(Enum):
3939
OPEN_FAIR = 'Open FAIR'
4040
OTHER = 'Other'
4141

42+
@staticmethod
43+
def get_from_vector(vector: str):
44+
if vector.startswith('CVSS:3.'):
45+
return VulnerabilitySourceType.CVSS_V3
46+
elif vector.startswith('CVSS:2.'):
47+
return VulnerabilitySourceType.CVSS_V2
48+
elif vector.startswith('OWASP'):
49+
return VulnerabilitySourceType.OWASP
50+
else:
51+
return VulnerabilitySourceType.OTHER
52+
53+
def get_localised_vector(self, vector: str) -> str:
54+
"""
55+
This method will remove any Source Scheme type from the supplied vector.
56+
57+
For example if VulnerabilitySourceType.OWASP
58+
59+
:param vector:
60+
:return:
61+
"""
62+
if self == VulnerabilitySourceType.CVSS_V3:
63+
return
64+
4265

4366
class VulnerabilitySeverity(Enum):
4467
"""
@@ -51,6 +74,27 @@ class VulnerabilitySeverity(Enum):
5174
CRITICAL = 'Critical'
5275
UNKNOWN = 'Unknown'
5376

77+
@staticmethod
78+
def get_from_cvss_scores(scores: tuple[float] = None):
79+
if type(scores) is float:
80+
scores = (scores, )
81+
82+
if scores is None:
83+
return VulnerabilitySeverity.UNKNOWN
84+
85+
max_cvss_score = max(scores)
86+
87+
if max_cvss_score >= 9.0:
88+
return VulnerabilitySeverity.CRITICAL
89+
elif max_cvss_score >= 7.0:
90+
return VulnerabilitySeverity.HIGH
91+
elif max_cvss_score >= 4.0:
92+
return VulnerabilitySeverity.MEDIUM
93+
elif max_cvss_score > 0.0:
94+
return VulnerabilitySeverity.LOW
95+
else:
96+
return VulnerabilitySeverity.NONE
97+
5498

5599
class VulnerabilityRating:
56100
"""
@@ -71,7 +115,10 @@ def __init__(self, score_base: float = None, score_impact: float = None, score_e
71115
self._score_exploitability = score_exploitability
72116
self._severity = severity
73117
self._method = method
74-
self._vector = vector
118+
if self._method:
119+
self._vector = self._method.get_localised_vector(vector=vector)
120+
else:
121+
self._vector = vector
75122

76123
def get_base_score(self) -> float:
77124
return self._score_base

tests/test_model_vulnerability.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from unittest import TestCase
22

3-
from cyclonedx.model.vulnerability import VulnerabilityRating
3+
from cyclonedx.model.vulnerability import VulnerabilityRating, VulnerabilitySeverity, VulnerabilitySourceType
44

55

66
class TestModelVulnerability(TestCase):
@@ -16,3 +16,63 @@ def test_v_rating_scores_base_only(self):
1616
def test_v_rating_scores_all(self):
1717
vr = VulnerabilityRating(score_base=1.0, score_impact=3.5, score_exploitability=5.6)
1818
self.assertTrue(vr.has_score())
19+
20+
def test_v_severity_from_cvss_scores_single_critical(self):
21+
self.assertEqual(
22+
VulnerabilitySeverity.get_from_cvss_scores(9.1),
23+
VulnerabilitySeverity.CRITICAL
24+
)
25+
26+
def test_v_severity_from_cvss_scores_multiple_critical(self):
27+
self.assertEqual(
28+
VulnerabilitySeverity.get_from_cvss_scores((9.1, 9.5)),
29+
VulnerabilitySeverity.CRITICAL
30+
)
31+
32+
def test_v_severity_from_cvss_scores_single_high(self):
33+
self.assertEqual(
34+
VulnerabilitySeverity.get_from_cvss_scores(8.9),
35+
VulnerabilitySeverity.HIGH
36+
)
37+
38+
def test_v_severity_from_cvss_scores_single_medium(self):
39+
self.assertEqual(
40+
VulnerabilitySeverity.get_from_cvss_scores(4.2),
41+
VulnerabilitySeverity.MEDIUM
42+
)
43+
44+
def test_v_severity_from_cvss_scores_single_low(self):
45+
self.assertEqual(
46+
VulnerabilitySeverity.get_from_cvss_scores(1.1),
47+
VulnerabilitySeverity.LOW
48+
)
49+
50+
def test_v_severity_from_cvss_scores_single_none(self):
51+
self.assertEqual(
52+
VulnerabilitySeverity.get_from_cvss_scores(0.0),
53+
VulnerabilitySeverity.NONE
54+
)
55+
56+
def test_v_severity_from_cvss_scores_multiple_high(self):
57+
self.assertEqual(
58+
VulnerabilitySeverity.get_from_cvss_scores((1.2, 8.9, 2.2, 5.6)),
59+
VulnerabilitySeverity.HIGH
60+
)
61+
62+
def test_v_source_parse_cvss3_1(self):
63+
self.assertEqual(
64+
VulnerabilitySourceType.get_from_vector('CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N'),
65+
VulnerabilitySourceType.CVSS_V3
66+
)
67+
68+
def test_v_source_parse_cvss2_1(self):
69+
self.assertEqual(
70+
VulnerabilitySourceType.get_from_vector('CVSS:2.0/AV:N/AC:L/Au:N/C:N/I:N/A:C'),
71+
VulnerabilitySourceType.CVSS_V2
72+
)
73+
74+
def test_v_source_parse_owasp_1(self):
75+
self.assertEqual(
76+
VulnerabilitySourceType.get_from_vector('OWASP/K9:M1:O0:Z2/D1:X1:W1:L3/C2:I1:A1:T1/F1:R1:S2:P3/50'),
77+
VulnerabilitySourceType.OWASP
78+
)

0 commit comments

Comments
 (0)