Skip to content

Commit c803db3

Browse files
committed
Group advisories with same content
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 053c8fb commit c803db3

File tree

3 files changed

+66
-10
lines changed

3 files changed

+66
-10
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.2.25 on 2026-02-10 12:46
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0112_alter_advisoryseverity_scoring_system_and_more"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="advisoryv2",
15+
name="precedence",
16+
field=models.IntegerField(
17+
blank=True,
18+
help_text="Precedence indicates the priority of advisory from different datasources. It is determined based on the reliability of the datasource and how close it is to the source.",
19+
null=True,
20+
),
21+
),
22+
]

vulnerabilities/models.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import csv
1111
import datetime
1212
import hashlib
13+
import json
1314
import logging
1415
import uuid
1516
import xml.etree.ElementTree as ET
@@ -68,7 +69,7 @@
6869
from vulnerabilities.importer import AdvisoryDataV2
6970
from vulnerabilities.severity_systems import EPSS
7071
from vulnerabilities.severity_systems import SCORING_SYSTEMS
71-
from vulnerabilities.utils import compute_patch_checksum
72+
from vulnerabilities.utils import compute_patch_checksum, normalize_list, normalize_text
7273
from vulnerabilities.utils import normalize_purl
7374
from vulnerabilities.utils import purl_to_dict
7475
from vulnerablecode import __version__ as VULNERABLECODE_VERSION
@@ -2988,11 +2989,11 @@ class AdvisoryV2(models.Model):
29882989
help_text="Weighted severity is the highest value calculated by multiplying each severity by its corresponding weight, divided by 10.",
29892990
)
29902991

2991-
# precedence = models.IntegerField(
2992-
# null=True,
2993-
# blank=True,
2994-
# help_text="Precedence indicates the priority level of addressing a vulnerability based on its overall risk",
2995-
# )
2992+
precedence = models.IntegerField(
2993+
null=True,
2994+
blank=True,
2995+
help_text="Precedence indicates the priority of advisory from different datasources. It is determined based on the reliability of the datasource and how close it is to the source.",
2996+
)
29962997

29972998
@property
29982999
def risk_score(self):
@@ -3057,6 +3058,35 @@ def get_aliases(self):
30573058
Return a queryset of all Aliases for this vulnerability.
30583059
"""
30593060
return self.aliases.all()
3061+
3062+
def compute_advisory_content(self):
3063+
"""
3064+
Compute a unique content hash for an advisory by normalizing its data and hashing it.
3065+
3066+
:param advisory: An Advisory object
3067+
:return: SHA-256 hash digest as content hash
3068+
"""
3069+
normalized_data = {
3070+
"summary": normalize_text(self.summary),
3071+
"impacted_packages": sorted(
3072+
[impact.to_dict() for impact in self.impacted_packages.all()],
3073+
key=lambda x: json.dumps(x, sort_keys=True),
3074+
),
3075+
"patches": sorted(
3076+
[patch.to_patch_data().to_dict() for patch in self.patches.all()],
3077+
key=lambda x: json.dumps(x, sort_keys=True),
3078+
),
3079+
"severities": sorted(
3080+
[sev.to_vulnerability_severity_data().to_dict() for sev in self.severities.all()],
3081+
key=lambda x: (x.get("system"), x.get("value")),
3082+
),
3083+
"weaknesses": normalize_list([weakness.cwe_id for weakness in self.weaknesses.all()]),
3084+
}
3085+
3086+
normalized_json = json.dumps(normalized_data, separators=(",", ":"), sort_keys=True)
3087+
content_hash = hashlib.sha256(normalized_json.encode("utf-8")).hexdigest()
3088+
3089+
return content_hash
30603090

30613091
alias = get_aliases
30623092

vulnerabilities/utils.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,12 +678,16 @@ def compute_content_id_v2(advisory_data):
678678
normalized_data = {
679679
"aliases": normalize_list(advisory_data.aliases),
680680
"summary": normalize_text(advisory_data.summary),
681-
"affected_packages": [
682-
pkg for pkg in normalize_list(advisory_data.affected_packages) if pkg
683-
],
681+
"impacted_packages": sorted(
682+
[impact.to_dict() for impact in advisory_data.impacted_packages.all()],
683+
key=lambda x: json.dumps(x, sort_keys=True),
684+
),
685+
"patches": sorted(
686+
[patch.to_patch_data().to_dict() for patch in advisory_data.patches.all()],
687+
key=lambda x: json.dumps(x, sort_keys=True),
688+
),
684689
"references": [ref for ref in normalize_list(advisory_data.references) if ref],
685690
"weaknesses": normalize_list(advisory_data.weaknesses),
686-
"patches": normalize_list(advisory_data.patches),
687691
}
688692
normalized_data["url"] = advisory_data.url
689693

0 commit comments

Comments
 (0)