Skip to content

Commit fef52fe

Browse files
committed
Add on impacted packages model
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent e7f19e8 commit fef52fe

7 files changed

Lines changed: 100 additions & 29 deletions

File tree

vulnerabilities/api_v3.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,13 @@ def create(self, request, *args, **kwargs):
450450
max_advisories = serializer.validated_data["max_advisories"]
451451

452452
if not purls:
453-
impacted = ImpactedPackageAffecting.objects.filter(package_id=OuterRef("id"))
453+
latest_impacts = ImpactedPackageAffecting.objects.filter(
454+
package_id=OuterRef("pk"),
455+
impacted_package__is_latest=True,
456+
)
454457

455458
query = (
456-
PackageV2.objects.annotate(has_vuln=Exists(impacted))
457-
.filter(has_vuln=True)
459+
PackageV2.objects.filter(Exists(latest_impacts))
458460
.values_list("package_url", flat=True)
459461
.order_by("package_url")
460462
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 5.2.11 on 2026-05-30 17:49
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("vulnerabilities", "0134_alter_advisoryset_unique_together"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="impactedpackage",
15+
name="is_latest",
16+
field=models.BooleanField(
17+
db_index=True,
18+
default=False,
19+
help_text="Indicates whether this is the latest impact for the advisory.",
20+
),
21+
),
22+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from django.db import migrations
2+
3+
4+
class Migration(migrations.Migration):
5+
6+
dependencies = [
7+
("vulnerabilities", "0135_impactedpackage_is_latest"),
8+
]
9+
10+
11+
def mark_latest_impacted_packages(apps, schema_editor):
12+
AdvisoryV2 = apps.get_model("vulnerabilities", "AdvisoryV2")
13+
ImpactedPackage = apps.get_model("vulnerabilities", "ImpactedPackage")
14+
15+
print("\nMarking impacted packages for latest V2 advisories as is_latest=True.")
16+
latest_advisory_ids = AdvisoryV2.objects.filter(is_latest=True).values_list("id", flat=True)
17+
ImpactedPackage.objects.filter(advisory_id__in=latest_advisory_ids).update(is_latest=True)
18+
19+
20+
operations = [
21+
migrations.RunPython(code=mark_latest_impacted_packages, reverse_code=migrations.RunPython.noop),
22+
]

vulnerabilities/models.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2934,53 +2934,63 @@ def latest_for_avids(self, avids):
29342934
return self.filter(avid__in=avids).latest_per_avid()
29352935

29362936
def latest_affecting_advisories_for_purl(self, purl):
2937-
adv_ids = ImpactedPackageAffecting.objects.filter(package__package_url=purl).values_list(
2938-
"impacted_package__advisory_id",
2939-
flat=True,
2940-
)
2937+
adv_ids = ImpactedPackage.objects.filter(
2938+
affecting_packages__package_url=purl, is_latest=True
2939+
).values_list("advisory_id", flat=True)
2940+
29412941
return self.filter(id__in=Subquery(adv_ids)).latest_per_avid()
29422942

29432943
def latest_affecting_advisories_for_purls(self, purls):
2944-
adv_ids = ImpactedPackageAffecting.objects.filter(
2945-
package__package_url__in=purls
2944+
adv_ids = ImpactedPackage.objects.filter(
2945+
affecting_packages__package_url__in=purls, is_latest=True
29462946
).values_list(
2947-
"impacted_package__advisory_id",
2947+
"advisory_id",
29482948
flat=True,
29492949
)
29502950
return self.filter(id__in=Subquery(adv_ids)).latest_per_avid()
29512951

29522952
def latest_affecting_advisories_for_packages(self, purls):
2953-
adv_ids = ImpactedPackageAffecting.objects.filter(package__in=purls).values_list(
2954-
"impacted_package__advisory_id",
2953+
adv_ids = ImpactedPackage.objects.filter(
2954+
affecting_packages__package_url__in=purls, is_latest=True
2955+
).values_list(
2956+
"advisory_id",
29552957
flat=True,
29562958
)
29572959
return self.filter(id__in=Subquery(adv_ids)).latest_per_avid()
29582960

29592961
def latest_fixed_by_advisories_for_purl(self, purl):
2960-
adv_ids = ImpactedPackageFixedBy.objects.filter(package__package_url=purl).values_list(
2961-
"impacted_package__advisory_id",
2962+
adv_ids = ImpactedPackage.objects.filter(
2963+
fixed_by_packages__package_url=purl, is_latest=True
2964+
).values_list(
2965+
"advisory_id",
29622966
flat=True,
29632967
)
29642968
return self.filter(id__in=Subquery(adv_ids)).latest_per_avid()
29652969

29662970
def latest_fixed_by_advisories_for_purls(self, purls):
2967-
adv_ids = ImpactedPackageFixedBy.objects.filter(package__package_url__in=purls).values_list(
2968-
"impacted_package__advisory_id",
2971+
adv_ids = ImpactedPackage.objects.filter(
2972+
fixed_by_packages__package_url__in=purls, is_latest=True
2973+
).values_list(
2974+
"advisory_id",
29692975
flat=True,
29702976
)
29712977

29722978
return self.filter(id__in=Subquery(adv_ids)).latest_per_avid()
29732979

29742980
def latest_advisories_for_purls(self, purls):
29752981
adv_ids = (
2976-
ImpactedPackageAffecting.objects.filter(package__package_url__in=purls)
2982+
ImpactedPackage.objects.filter(
2983+
affecting_packages__package_url__in=purls, is_latest=True
2984+
)
29772985
.values_list(
2978-
"impacted_package__advisory_id",
2986+
"advisory_id",
29792987
flat=True,
29802988
)
29812989
.union(
2982-
ImpactedPackageFixedBy.objects.filter(package__package_url__in=purls).values_list(
2983-
"impacted_package__advisory_id",
2990+
ImpactedPackage.objects.filter(
2991+
fixed_by_packages__package_url__in=purls, is_latest=True
2992+
).values_list(
2993+
"advisory_id",
29842994
flat=True,
29852995
)
29862996
)
@@ -2991,14 +3001,16 @@ def latest_advisories_for_purls(self, purls):
29913001

29923002
def latest_advisories_for_purl(self, purl):
29933003
adv_ids = (
2994-
ImpactedPackageAffecting.objects.filter(package__package_url=purl)
3004+
ImpactedPackage.objects.filter(affecting_packages__package_url=purl, is_latest=True)
29953005
.values_list(
2996-
"impacted_package__advisory_id",
3006+
"advisory_id",
29973007
flat=True,
29983008
)
29993009
.union(
3000-
ImpactedPackageFixedBy.objects.filter(package__package_url=purl).values_list(
3001-
"impacted_package__advisory_id",
3010+
ImpactedPackage.objects.filter(
3011+
fixed_by_packages__package_url=purl, is_latest=True
3012+
).values_list(
3013+
"advisory_id",
30023014
flat=True,
30033015
)
30043016
)
@@ -3352,6 +3364,14 @@ class ImpactedPackage(models.Model):
33523364
help_text="Timestamp of the last successful vers range unfurl.",
33533365
)
33543366

3367+
is_latest = models.BooleanField(
3368+
default=False,
3369+
blank=False,
3370+
null=False,
3371+
db_index=True,
3372+
help_text="Indicates whether this is the latest impact for the advisory.",
3373+
)
3374+
33553375
def to_dict(self):
33563376
from vulnerabilities.utils import purl_to_dict
33573377

vulnerabilities/pipes/advisory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ def insert_advisory_v2(
374374
advisory_obj.risk_score = round(risk_score, 1) if risk_score is not None else None
375375
advisory_obj.save()
376376

377+
# Set all impacts as is_latest=False for the advisory before creating new impacts with is_latest=True. This ensures that only the impacts related to the latest advisory are marked as latest.
378+
ImpactedPackage.objects.filter(advisory__avid=advisory_obj.avid).update(is_latest=False)
377379
for affected_pkg in advisory.affected_packages:
378380
impact = ImpactedPackage.objects.create(
379381
advisory=advisory_obj,
@@ -386,6 +388,7 @@ def insert_advisory_v2(
386388
fixed_vers=(
387389
str(affected_pkg.fixed_version_range) if affected_pkg.fixed_version_range else None
388390
),
391+
is_latest=True,
389392
)
390393
package_affected_purls, package_fixed_purls = get_exact_purls_v2(
391394
affected_package=affected_pkg,

vulnerabilities/pipes/export.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ def package_prefetched_qs(checkpoint):
2525
.prefetch_related(
2626
Prefetch(
2727
"affected_in_impacts",
28-
queryset=ImpactedPackage.objects.only("advisory_id").prefetch_related(
28+
queryset=ImpactedPackage.objects.filter(is_latest=True)
29+
.only("advisory_id")
30+
.prefetch_related(
2931
Prefetch(
3032
"advisory",
3133
queryset=AdvisoryV2.objects.only("avid"),
@@ -34,7 +36,9 @@ def package_prefetched_qs(checkpoint):
3436
),
3537
Prefetch(
3638
"fixed_in_impacts",
37-
queryset=ImpactedPackage.objects.only("advisory_id").prefetch_related(
39+
queryset=ImpactedPackage.objects.filter(is_latest=True)
40+
.only("advisory_id")
41+
.prefetch_related(
3842
Prefetch(
3943
"advisory",
4044
queryset=AdvisoryV2.objects.only("avid"),

vulnerabilities/pipes/group_advisories.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,7 @@ def delete_and_save_advisory_set(groups, package, relation=None):
6363
print(f"Successfully saved advisory sets for package: {package.purl}")
6464

6565

66-
def group_advisory_for_package(
67-
package, logger=None
68-
):
66+
def group_advisory_for_package(package, logger=None):
6967
"""
7068
Group advisories for a given package and save the advisory sets for the package.
7169
"""

0 commit comments

Comments
 (0)