Skip to content

Commit 9203c39

Browse files
authored
Merge pull request #2366 from aboutcode-org/validate-cvss-score
Avoid converting None to "None" in AdvisorySeverity
2 parents d316aaf + 2980275 commit 9203c39

9 files changed

Lines changed: 327 additions & 130 deletions

File tree

docs/source/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"http://ftp.suse.com/pub/projects/security/yaml/",
4343
r"https://nixos\.wiki/", # NixOS wiki blocks CI bots with 403
4444
"https://usn.ubuntu.com/usn-db/database-all.json.bz2",
45+
"https://public.vulnerablecode.io/vulnerabilities/search/",
4546
]
4647

4748
# Add any Sphinx extension module names here, as strings. They can be

vulnerabilities/importer.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from typing import Optional
2020
from typing import Set
2121
from typing import Tuple
22-
from typing import Union
2322

2423
import pytz
2524
from dateutil import parser as dateparser
@@ -40,7 +39,6 @@
4039
from vulnerabilities.utils import get_reference_id
4140
from vulnerabilities.utils import is_commit
4241
from vulnerabilities.utils import is_cve
43-
from vulnerabilities.utils import nearest_patched_package
4442
from vulnerabilities.utils import purl_to_dict
4543
from vulnerabilities.utils import update_purl_version
4644

@@ -65,7 +63,7 @@ def __post_init__(self):
6563
raise TypeError(f"system must be a ScoringSystem, got {type(self.system)!r}")
6664

6765
if not isinstance(self.value, str):
68-
self.value = str(self.value)
66+
self.value = str(self.value) if self.value else ""
6967

7068
def to_dict(self):
7169
data = {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Generated by Django 5.2.11 on 2026-07-02 09:50
2+
3+
from django.db import migrations
4+
5+
from vulnerabilities.importer import AdvisoryDataV2
6+
from vulnerabilities.importer import AffectedPackageV2
7+
from vulnerabilities.importer import PatchData
8+
from vulnerabilities.importer import ReferenceV2
9+
from vulnerabilities.importer import VulnerabilitySeverity
10+
from vulnerabilities.utils import compute_content_id_v2
11+
from vulnerabilities.utils import normalize_list
12+
from vulnerabilities.utils import purl_to_dict
13+
14+
15+
class Migration(migrations.Migration):
16+
17+
dependencies = [
18+
("vulnerabilities", "0138_fix_malformed_cvss_vector"),
19+
]
20+
21+
def cleanup_none_severity_string(apps, schema_editor):
22+
AdvisorySeverity = apps.get_model("vulnerabilities", "AdvisorySeverity")
23+
AdvisoryV2 = apps.get_model("vulnerabilities", "AdvisoryV2")
24+
batch = []
25+
batch_size = 5000
26+
27+
advisory_ids = list(
28+
AdvisoryV2.objects.filter(severities__value="None")
29+
.distinct()
30+
.values_list("id", flat=True)
31+
)
32+
33+
AdvisorySeverity.objects.filter(value="None").update(value="")
34+
35+
for advisory in AdvisoryV2.objects.filter(id__in=advisory_ids).iterator(chunk_size=2000):
36+
advisory.unique_content_id = compute_content_id_v2(to_advisory_data(advisory))
37+
batch.append(advisory)
38+
39+
if len(batch) >= batch_size:
40+
AdvisoryV2.objects.bulk_update(batch, ["unique_content_id"])
41+
batch.clear()
42+
43+
if batch:
44+
AdvisoryV2.objects.bulk_update(batch, ["unique_content_id"])
45+
46+
operations = [
47+
migrations.RunPython(
48+
cleanup_none_severity_string,
49+
reverse_code=migrations.RunPython.noop,
50+
),
51+
]
52+
53+
54+
def commit_patch_to_dict(patch):
55+
return {
56+
"vcs_url": patch.vcs_url,
57+
"commit_hash": patch.commit_hash,
58+
"patch_text": patch.patch_text,
59+
"patch_checksum": patch.patch_checksum,
60+
}
61+
62+
63+
def to_affected_package_data(impact):
64+
"""Return `AffectedPackageV2` data from the impact."""
65+
return AffectedPackageV2.from_dict(
66+
{
67+
"package": purl_to_dict(impact.base_purl),
68+
"affected_version_range": impact.affecting_vers,
69+
"fixed_version_range": impact.fixed_vers,
70+
"introduced_by_commit_patches": [
71+
commit_patch_to_dict(commit)
72+
for commit in impact.introduced_by_package_commit_patches.all()
73+
],
74+
"fixed_by_commit_patches": [
75+
commit_patch_to_dict(commit)
76+
for commit in impact.fixed_by_package_commit_patches.all()
77+
],
78+
}
79+
)
80+
81+
82+
def to_patch_data(patch):
83+
"""Return `PatchData` from the Patch."""
84+
85+
return PatchData.from_dict(
86+
{
87+
"patch_url": patch.patch_url,
88+
"patch_text": patch.patch_text,
89+
"patch_checksum": patch.patch_checksum,
90+
}
91+
)
92+
93+
94+
def to_reference_v2_data(ref):
95+
return ReferenceV2.from_dict(
96+
{
97+
"reference_id": ref.reference_id,
98+
"reference_type": ref.reference_type,
99+
"url": ref.url,
100+
}
101+
)
102+
103+
104+
def to_vulnerability_severity_data(severity):
105+
return VulnerabilitySeverity.from_dict(
106+
{
107+
"system": severity.scoring_system,
108+
"value": severity.value,
109+
"scoring_elements": severity.scoring_elements,
110+
"published_at": severity.published_at,
111+
"url": severity.url,
112+
}
113+
)
114+
115+
116+
def to_advisory_data(advisory):
117+
return AdvisoryDataV2(
118+
advisory_id=advisory.advisory_id,
119+
aliases=normalize_list([item.alias for item in advisory.aliases.all()]),
120+
summary=advisory.summary,
121+
affected_packages=normalize_list(
122+
[to_affected_package_data(impacted) for impacted in advisory.impacted_packages.all()]
123+
),
124+
references=normalize_list([to_reference_v2_data(ref) for ref in advisory.references.all()]),
125+
patches=normalize_list([to_patch_data(patch) for patch in advisory.patches.all()]),
126+
date_published=advisory.date_published,
127+
weaknesses=normalize_list([weak.cwe_id for weak in advisory.weaknesses.all()]),
128+
severities=normalize_list(
129+
[to_vulnerability_severity_data(sev) for sev in advisory.severities.all()]
130+
),
131+
url=advisory.url,
132+
)

vulnerabilities/models.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
import csv
1111
import datetime
12-
import hashlib
13-
import json
1412
import logging
1513
import uuid
1614
import xml.etree.ElementTree as ET

vulnerabilities/tests/pipelines/v2_improvers/test_compute_advisory_todo_v2.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from vulnerabilities.pipelines.v2_improvers.compute_advisory_todo import ComputeToDo
2727
from vulnerabilities.pipes.advisory import insert_advisory_v2
2828
from vulnerabilities.tests.pipelines import TestLogger
29+
from vulnerabilities.utils import canonical_value
2930

3031

3132
class TestComputeToDo(TestCase):
@@ -690,7 +691,10 @@ def test_todo_conflict_details_partial_curation_unpaired_purl_and_conflicting_af
690691
result_partial_curation = issue_details["partial_curation_advisory"]
691692
self.assertEqual(1, AdvisoryToDoV2.objects.count())
692693
self.assertEqual("CONFLICTING_AFFECTED_PACKAGES", todo.issue_type)
693-
self.assertDictEqual(expected_partial_curation_advisory, result_partial_curation)
694+
self.assertCountEqual(
695+
expected_partial_curation_advisory["affected_packages"],
696+
result_partial_curation["affected_packages"],
697+
)
694698

695699
def test_todo_conflicting_severity(self):
696700
insert_advisory_v2(

vulnerabilities/tests/test_data_migrations.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
from vulnerabilities.importer import AdvisoryData
2424
from vulnerabilities.importer import AffectedPackage
2525
from vulnerabilities.importer import Reference
26+
from vulnerabilities.models import AdvisorySeverity
27+
from vulnerabilities.models import AdvisoryV2
28+
from vulnerabilities.models import ImpactedPackage
29+
from vulnerabilities.utils import compute_content_id_v2
2630
from vulnerabilities.utils import purl_to_dict
2731

2832

@@ -1378,3 +1382,50 @@ def test_scheme_migration_correctness(self):
13781382

13791383
self.assertEqual(self.impact2.affecting_vers, "vers:apk/3.4.5")
13801384
self.assertEqual(self.impact2.fixed_vers, None)
1385+
1386+
1387+
class TestCleanAdvisorySeverityMigration(TestMigrations):
1388+
app_name = "vulnerabilities"
1389+
migrate_from = "0138_fix_malformed_cvss_vector"
1390+
migrate_to = "0139_cleanup_none_string_in_severity"
1391+
1392+
def setUpBeforeMigration(self, apps):
1393+
# AdvisoryV2 = apps.get_model("vulnerabilities", "AdvisoryV2")
1394+
# ImpactedPackage = apps.get_model("vulnerabilities", "ImpactedPackage")
1395+
# AdvisorySeverity = apps.get_model("vulnerabilities", "AdvisorySeverity")
1396+
1397+
self.advisory1 = AdvisoryV2.objects.create(
1398+
unique_content_id="b001d1a8952bc056d0161f1dd45dd8f90b25f62c56a887ea21d09fafd78a0f61",
1399+
url="https://old.example.com",
1400+
summary="Old advisory",
1401+
advisory_id="test_adv1",
1402+
avid="test_pipeline/test_adv",
1403+
datasource_id="test_pipeline",
1404+
pipeline_id="test_pipeline_v2",
1405+
)
1406+
1407+
ImpactedPackage.objects.create(
1408+
advisory=self.advisory1,
1409+
base_purl="pkg:npm/foobar0",
1410+
affecting_vers="vers:npm/4.3.2",
1411+
fixed_vers="vers:npm/5.0.0",
1412+
)
1413+
1414+
self.severity = AdvisorySeverity.objects.create(
1415+
scoring_system=severity_systems.CVSSV4,
1416+
scoring_elements="CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:P/VC:N/VI:N/VA:N",
1417+
value="None",
1418+
)
1419+
1420+
self.advisory1.severities.add(self.severity)
1421+
1422+
def test_severity_value_cleaned(self):
1423+
self.severity.refresh_from_db()
1424+
self.assertEqual(self.severity.value, "")
1425+
1426+
def test_advisory_content_id_recomputed(self):
1427+
self.advisory1.refresh_from_db()
1428+
self.assertEqual(
1429+
self.advisory1.unique_content_id,
1430+
"a15d4651cb05e3513c12263a11e34bd9103f68833cac8f7ffdbbd71b9cb4cf16",
1431+
)

vulnerabilities/tests/test_importer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from vulnerabilities.importer import PackageCommitPatchData
1414
from vulnerabilities.importer import PatchData
1515
from vulnerabilities.importer import ReferenceV2
16+
from vulnerabilities.importer import VulnerabilitySeverity
1617
from vulnerabilities.pipes.advisory import classify_patch_source
1718

1819

@@ -201,3 +202,15 @@ def test_classify_patch_source_integration(url, commit_hash, patch_text, results
201202
assert actual_data_obj.reference_id == expected_data_obj.reference_id
202203
assert actual_data_obj.reference_type == expected_data_obj.reference_type
203204
assert actual_data_obj.url == expected_data_obj.url
205+
206+
207+
def test_vulnerability_severity_value_string_conversion():
208+
severity = VulnerabilitySeverity.from_dict(
209+
{
210+
"system": "cvssv3",
211+
"value": None,
212+
"scoring_elements": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
213+
}
214+
)
215+
216+
assert severity.value == ""

0 commit comments

Comments
 (0)