Skip to content

Commit ec5b972

Browse files
committed
Resolve migration conflict & add weighted_severity, exploitability to api_v2
Signed-off-by: ziad hany <[email protected]>
1 parent fbc8fed commit ec5b972

File tree

7 files changed

+109
-83
lines changed

7 files changed

+109
-83
lines changed

vulnerabilities/api_v2.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class VulnerabilityV2Serializer(serializers.ModelSerializer):
6767
weaknesses = WeaknessV2Serializer(many=True)
6868
references = VulnerabilityReferenceV2Serializer(many=True, source="vulnerabilityreference_set")
6969
severities = VulnerabilitySeverityV2Serializer(many=True)
70+
exploitability = serializers.FloatField(read_only=True)
71+
weighted_severity = serializers.FloatField(read_only=True)
72+
risk_score = serializers.FloatField(read_only=True)
7073

7174
class Meta:
7275
model = Vulnerability
@@ -77,6 +80,9 @@ class Meta:
7780
"severities",
7881
"weaknesses",
7982
"references",
83+
"exploitability",
84+
"weighted_severity",
85+
"risk_score",
8086
]
8187

8288
def get_aliases(self, obj):

vulnerabilities/migrations/0078_vulnerability_exploitability_and_more.py

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Generated by Django 4.2.16 on 2024-11-16 20:41
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0081_alter_packagechangelog_software_version_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="vulnerability",
15+
name="exploitability",
16+
field=models.DecimalField(
17+
decimal_places=1,
18+
help_text='"Exploitability indicates the likelihood that a vulnerability in a software package could be used \n by malicious actors to compromise systems, applications, or networks. \n This metric is determined automatically based on the discovery of known exploits.',
19+
max_digits=2,
20+
null=True,
21+
),
22+
),
23+
migrations.AddField(
24+
model_name="vulnerability",
25+
name="weighted_severity",
26+
field=models.DecimalField(
27+
decimal_places=1,
28+
help_text="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10",
29+
max_digits=3,
30+
null=True,
31+
),
32+
),
33+
migrations.AlterField(
34+
model_name="package",
35+
name="risk_score",
36+
field=models.DecimalField(
37+
decimal_places=1,
38+
help_text="Risk score between 0.00 and 10.00, where higher values indicate greater vulnerability risk for the package.",
39+
max_digits=3,
40+
null=True,
41+
),
42+
),
43+
]

vulnerabilities/models.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,34 @@ class Vulnerability(models.Model):
243243
related_name="vulnerabilities",
244244
)
245245

246+
exploitability = models.DecimalField(
247+
null=True,
248+
max_digits=2,
249+
decimal_places=1,
250+
help_text=""""Exploitability indicates the likelihood that a vulnerability in a software package could be used
251+
by malicious actors to compromise systems, applications, or networks.
252+
This metric is determined automatically based on the discovery of known exploits.""",
253+
)
254+
255+
weighted_severity = models.DecimalField(
256+
null=True,
257+
max_digits=3,
258+
decimal_places=1,
259+
help_text="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10",
260+
)
261+
262+
@property
263+
def risk_score(self):
264+
"""
265+
Risk expressed as a number ranging from 0 to 10.
266+
Risk is calculated from weighted severity and exploitability values.
267+
It is the maximum value of (the weighted severity multiplied by its exploitability) or 10
268+
Risk = min(weighted severity * exploitability, 10)
269+
"""
270+
if self.exploitability and self.weighted_severity:
271+
risk_score = min(float(self.exploitability * self.weighted_severity), 10.0)
272+
return round(risk_score, 1)
273+
246274
objects = VulnerabilityQuerySet.as_manager()
247275

248276
class Meta:
@@ -672,8 +700,8 @@ class Package(PackageURLMixin):
672700

673701
risk_score = models.DecimalField(
674702
null=True,
675-
max_digits=4,
676-
decimal_places=2,
703+
max_digits=3,
704+
decimal_places=1,
677705
help_text="Risk score between 0.00 and 10.00, where higher values "
678706
"indicate greater vulnerability risk for the package.",
679707
)

vulnerabilities/pipelines/compute_package_risk.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ def steps(cls):
3434
)
3535

3636
def compute_and_store_vulnerability_risk_score(self):
37-
affected_vulnerabilities = (
38-
Vulnerability.objects.filter(affectedbypackagerelatedvulnerability__isnull=False)
39-
.prefetch_related("references")
40-
.only("references", "exploits")
37+
affected_vulnerabilities = Vulnerability.objects.filter(
38+
affectedbypackagerelatedvulnerability__isnull=False
39+
).prefetch_related(
40+
"references",
41+
"exploits",
4142
)
4243

4344
self.log(
@@ -51,8 +52,8 @@ def compute_and_store_vulnerability_risk_score(self):
5152
batch_size = 5000
5253

5354
for vulnerability in progress.iter(affected_vulnerabilities.paginated()):
54-
references = vulnerability.references
55-
severities = vulnerability.severities.select_related("reference")
55+
severities = vulnerability.severities.all()
56+
references = vulnerability.references.all()
5657

5758
(
5859
vulnerability.weighted_severity,
@@ -76,17 +77,8 @@ def compute_and_store_vulnerability_risk_score(self):
7677

7778
def compute_and_store_package_risk_score(self):
7879
affected_packages = (
79-
Package.objects.filter(affected_by_vulnerabilities__isnull=False).prefetch_related(
80-
"affectedbypackagerelatedvulnerability_set__vulnerability",
81-
"affectedbypackagerelatedvulnerability_set__vulnerability__references",
82-
"affectedbypackagerelatedvulnerability_set__vulnerability__severities",
83-
"affectedbypackagerelatedvulnerability_set__vulnerability__exploits",
84-
)
85-
).distinct()
86-
87-
affected_packages = Package.objects.filter(
88-
affected_by_vulnerabilities__isnull=False
89-
).distinct()
80+
Package.objects.filter(affected_by_vulnerabilities__isnull=False).only("id").distinct()
81+
)
9082

9183
self.log(f"Calculating risk for {affected_packages.count():,d} affected package records")
9284

vulnerabilities/risk.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,22 @@
66
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9-
10-
9+
from typing import List
1110
from urllib.parse import urlparse
1211

1312
from django.db.models import Prefetch
1413

1514
from vulnerabilities.models import AffectedByPackageRelatedVulnerability
16-
from vulnerabilities.models import Exploit
17-
from vulnerabilities.models import Package
1815
from vulnerabilities.models import Vulnerability
1916
from vulnerabilities.models import VulnerabilityReference
17+
from vulnerabilities.models import VulnerabilitySeverity
2018
from vulnerabilities.severity_systems import EPSS
2119
from vulnerabilities.weight_config import WEIGHT_CONFIG
2220

2321
DEFAULT_WEIGHT = 5
2422

2523

26-
def get_weighted_severity(severities):
24+
def get_weighted_severity(severities: List[VulnerabilitySeverity]):
2725
"""
2826
Weighted Severity is the maximum value obtained when each Severity is multiplied
2927
by its associated Weight/10.
@@ -57,7 +55,9 @@ def get_weighted_severity(severities):
5755
vul_score_value = score_map.get(vul_score, 0) * max_weight
5856

5957
score_list.append(vul_score_value)
60-
return max(score_list) if score_list else 0
58+
59+
max_score = max(score_list) if score_list else 0
60+
return round(max_score, 1)
6161

6262

6363
def get_exploitability_level(exploits, references, severities):
@@ -99,12 +99,8 @@ def compute_vulnerability_risk_factors(references, severities, exploits):
9999
100100
Risk = min(weighted severity * exploitability, 10)
101101
"""
102-
severities = severities.all()
103-
exploits = exploits.all()
104-
reference = references.all()
105-
106102
weighted_severity = get_weighted_severity(severities)
107-
exploitability = get_exploitability_level(exploits, reference, severities)
103+
exploitability = get_exploitability_level(exploits, references, severities)
108104
return weighted_severity, exploitability
109105

110106

@@ -130,4 +126,4 @@ def compute_package_risk(package):
130126
if not result:
131127
return
132128

133-
return f"{max(result):.2f}"
129+
return round(max(result), 1)

vulnerabilities/tests/test_risk.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_exploitability_level(
131131
@pytest.mark.django_db
132132
def test_get_weighted_severity(vulnerability):
133133
severities = vulnerability.severities.all()
134-
assert get_weighted_severity(severities) == 6.210000000000001
134+
assert get_weighted_severity(severities) == 6.2
135135

136136
severity2 = VulnerabilitySeverity.objects.create(
137137
url="https://security-tracker.debian.org/tracker/CVE-2019-13057",
@@ -146,21 +146,17 @@ def test_get_weighted_severity(vulnerability):
146146

147147
@pytest.mark.django_db
148148
def test_compute_vulnerability_risk_factors(vulnerability):
149-
assert compute_vulnerability_risk_factors(
150-
vulnerability.references, vulnerability.severities, vulnerability.exploits
151-
) == (6.210000000000001, 2)
152-
assert compute_vulnerability_risk_factors(
153-
vulnerability.references, vulnerability.severities, None
154-
) == (
155-
6.210000000000001,
156-
0.5,
157-
)
158-
assert compute_vulnerability_risk_factors(
159-
vulnerability.references, None, vulnerability.exploits
160-
) == (
161-
0,
149+
severities = vulnerability.severities.all()
150+
references = vulnerability.references.all()
151+
152+
assert compute_vulnerability_risk_factors(references, severities, vulnerability.exploits) == (
153+
6.2,
162154
2,
163155
)
156+
157+
assert compute_vulnerability_risk_factors(references, severities, None) == (6.2, 0.5)
158+
assert compute_vulnerability_risk_factors(references, None, vulnerability.exploits) == (0, 2)
159+
164160
assert compute_vulnerability_risk_factors(None, None, None) == (0, 0.5)
165161

166162

@@ -169,15 +165,15 @@ def test_get_vulnerability_risk_score(vulnerability):
169165
vulnerability.weighted_severity = 6.0
170166
vulnerability.exploitability = 2
171167

172-
assert vulnerability.risk_score == "10" # max risk_score can be reached
168+
assert vulnerability.risk_score == 10.0 # max risk_score can be reached
173169

174170
vulnerability.weighted_severity = 6
175171
vulnerability.exploitability = 0.5
176-
assert vulnerability.risk_score == "3"
172+
assert vulnerability.risk_score == 3.0
177173

178174
vulnerability.weighted_severity = 5.6
179175
vulnerability.exploitability = 0.5
180-
assert vulnerability.risk_score == "2.8"
176+
assert vulnerability.risk_score == 2.8
181177

182178
vulnerability.weighted_severity = None
183179
vulnerability.exploitability = 0.5

0 commit comments

Comments
 (0)