From f8826278b67c5ddd32ca5025c60a6b60ef75729f Mon Sep 17 00:00:00 2001 From: Keshav Priyadarshi Date: Wed, 22 Jul 2026 23:47:21 +0530 Subject: [PATCH 1/3] feat: add model to store advisory extra mitigation Signed-off-by: Keshav Priyadarshi --- .../migrations/0141_advisorymitigations.py | 94 +++++++++++++++++++ vulnerabilities/models.py | 61 ++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 vulnerabilities/migrations/0141_advisorymitigations.py diff --git a/vulnerabilities/migrations/0141_advisorymitigations.py b/vulnerabilities/migrations/0141_advisorymitigations.py new file mode 100644 index 000000000..227380b98 --- /dev/null +++ b/vulnerabilities/migrations/0141_advisorymitigations.py @@ -0,0 +1,94 @@ +# Generated by Django 5.2.11 on 2026-07-22 18:14 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("vulnerabilities", "0140_alter_advisorytodo_issue_type_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="AdvisoryMitigations", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "base_purl", + models.CharField( + help_text="Version less PURL related to mitigation.", max_length=500 + ), + ), + ("upgrade_to_versions_note", models.TextField(blank=True)), + ("downgrade_to_versions_note", models.TextField(blank=True)), + ("patches_note", models.TextField(blank=True)), + ( + "config_change", + models.TextField( + blank=True, help_text="Configuration change to mitigate the advisory." + ), + ), + ("config_change_note", models.TextField(blank=True)), + ( + "filter_ports_ips", + models.TextField( + blank=True, help_text="Filter Ports and IPs to mitigate the advisory." + ), + ), + ("filter_ports_ips_note", models.TextField(blank=True)), + ("replace_package_note", models.TextField(blank=True)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "advisory", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="mitigations", + to="vulnerabilities.advisoryv2", + ), + ), + ( + "downgrade_to_versions", + models.ManyToManyField( + blank=True, + help_text="Packages that mitigates the advisory by downgrading.", + related_name="downgrade_mitigations", + to="vulnerabilities.packagev2", + ), + ), + ( + "patches", + models.ManyToManyField( + help_text="Patches to mitigate the advisory.", + related_name="patch_mitigations", + to="vulnerabilities.packagecommitpatch", + ), + ), + ( + "replace_with_packages", + models.ManyToManyField( + blank=True, + help_text="Packages that can replace the vulnerable package.", + related_name="replacement_mitigations", + to="vulnerabilities.packagev2", + ), + ), + ( + "upgrade_to_versions", + models.ManyToManyField( + blank=True, + help_text="Packages that mitigates the advisory by upgrading.", + related_name="upgrade_mitigations", + to="vulnerabilities.packagev2", + ), + ), + ], + ), + ] diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 0787cdd45..826d35a54 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -3470,6 +3470,67 @@ class Meta: ] +class AdvisoryMitigations(models.Model): + + advisory = models.ForeignKey( + AdvisoryV2, + related_name="mitigations", + on_delete=models.CASCADE, + ) + + base_purl = models.CharField( + max_length=500, + blank=False, + help_text="Version less PURL related to mitigation.", + ) + + upgrade_to_versions = models.ManyToManyField( + "PackageV2", + blank=True, + related_name="upgrade_mitigations", + help_text="Packages that mitigates the advisory by upgrading.", + ) + upgrade_to_versions_note = models.TextField(blank=True) + + downgrade_to_versions = models.ManyToManyField( + "PackageV2", + blank=True, + related_name="downgrade_mitigations", + help_text="Packages that mitigates the advisory by downgrading.", + ) + downgrade_to_versions_note = models.TextField(blank=True) + + patches = models.ManyToManyField( + "PackageCommitPatch", + related_name="patch_mitigations", + help_text="Patches to mitigate the advisory.", + ) + patches_note = models.TextField(blank=True) + + config_change = models.TextField( + blank=True, + help_text="Configuration change to mitigate the advisory.", + ) + config_change_note = models.TextField(blank=True) + + filter_ports_ips = models.TextField( + blank=True, + help_text="Filter Ports and IPs to mitigate the advisory.", + ) + filter_ports_ips_note = models.TextField(blank=True) + + replace_with_packages = models.ManyToManyField( + "PackageV2", + blank=True, + related_name="replacement_mitigations", + help_text="Packages that can replace the vulnerable package.", + ) + replace_package_note = models.TextField(blank=True) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class ToDoRelatedAdvisory(models.Model): todo = models.ForeignKey( AdvisoryToDo, From 8f8a3f681d8550c53f77c04b3cc970d2dc2fd741 Mon Sep 17 00:00:00 2001 From: Keshav Priyadarshi Date: Wed, 22 Jul 2026 23:48:32 +0530 Subject: [PATCH 2/3] feat: add view for mitigation curation Signed-off-by: Keshav Priyadarshi --- vulnerabilities/views.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index cb2585657..73eb229b3 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -1219,3 +1219,29 @@ def get_context_data(self, **kwargs): context["vulnerability_id"] = todo.alias context["curation_items"] = json.dumps(todo.issue_detail["curation_items"]) return context + + +class AdvisoryMitigationCurationView(DetailView): + model = AdvisoryToDoV2 + template_name = "mitigation_curation.html" + slug_url_kwarg = "todo_id" + slug_field = "todo_id" + + def get_queryset(self): + return super().get_queryset().filter(issue_type="MISSING_FIXED_BY_PACKAGE") + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + todo = self.object + + context["advisory_summaries"] = { + adv.avid: adv.summary for adv in todo.advisories.all() if adv.summary.strip() + } + context["avid"] = next(iter(context["advisory_summaries"])) + context["curation_items"] = [ + a.base_purl + for a in todo.advisories.first().impacted_packages.filter( + fixed_by_packages__isnull=True + ) + ] + return context From 64c1c20d5d11c9a2630e749e866df6e102efbd3c Mon Sep 17 00:00:00 2001 From: Keshav Priyadarshi Date: Wed, 22 Jul 2026 23:54:17 +0530 Subject: [PATCH 3/3] feat(ui): add template for mitigation curation Signed-off-by: Keshav Priyadarshi --- vulnerabilities/templates/advisory_todos.html | 2 + .../templates/mitigation_curation.html | 365 ++++++++++++++++++ .../templates/weakness_curation.html | 1 + vulnerablecode/urls.py | 6 + 4 files changed, 374 insertions(+) create mode 100644 vulnerabilities/templates/mitigation_curation.html diff --git a/vulnerabilities/templates/advisory_todos.html b/vulnerabilities/templates/advisory_todos.html index f3eca864a..d77a5af77 100644 --- a/vulnerabilities/templates/advisory_todos.html +++ b/vulnerabilities/templates/advisory_todos.html @@ -129,6 +129,8 @@

Advisory To-Dos

{% elif todo.issue_type == "CONFLICTING_WEAKNESSES" %} + {% elif todo.issue_type == "MISSING_FIXED_BY_PACKAGE" %} + {% endif %}
diff --git a/vulnerabilities/templates/mitigation_curation.html b/vulnerabilities/templates/mitigation_curation.html new file mode 100644 index 000000000..1628f499b --- /dev/null +++ b/vulnerabilities/templates/mitigation_curation.html @@ -0,0 +1,365 @@ +{% extends "base.html" %} +{% load static %} +{% load utils %} + +{% block extrahead %} + +{% endblock %} + +{% block content %} +
+
+
+ + +
+
+
+ + Progress + + 0 / 0 +
+ +
+
+ +
+ +
+
+

Advisory Summary

+
+ {% for avid, text in advisory_summaries.items %} +
+

{{ avid }}

+
{{ text|normalize_links|urlize }}
+
+ {% empty %} +
+ No summaries available. +
+ {% endfor %} +
+
+ +
+
+
+

+ +
+ +
+ + + + + + + + + + + + +
Mitigation TypeMitigation DataMitigation Note
+
+ +
+
+ +
+
+ + +
+
+
+
+
+
+
+{% endblock %} + + +{% block scripts %} + +{% endblock %} diff --git a/vulnerabilities/templates/weakness_curation.html b/vulnerabilities/templates/weakness_curation.html index b027861dd..f29419476 100644 --- a/vulnerabilities/templates/weakness_curation.html +++ b/vulnerabilities/templates/weakness_curation.html @@ -153,6 +153,7 @@ .table-container { overflow-x: auto; + overflow-y: auto; max-height: 800px; border: 1px solid #dbdbdb; border-radius: 6px; diff --git a/vulnerablecode/urls.py b/vulnerablecode/urls.py index ae4cab86c..b693bd725 100644 --- a/vulnerablecode/urls.py +++ b/vulnerablecode/urls.py @@ -23,6 +23,7 @@ from vulnerabilities.api_v3 import PackageV3ViewSet from vulnerabilities.views import AdminLoginView from vulnerabilities.views import AdvisoryDetails +from vulnerabilities.views import AdvisoryMitigationCurationView from vulnerabilities.views import AdvisoryPackageCommitPatchDetails from vulnerabilities.views import AdvisoryPackageCurationView from vulnerabilities.views import AdvisoryPackagesDetails @@ -98,6 +99,11 @@ def __init__(self, *args, **kwargs): AdvisoryWeaknessCurationView.as_view(), name="todo-weakness-detail", ), + path( + "advisories/todos//mitigation/curate/", + AdvisoryMitigationCurationView.as_view(), + name="todo-mitigation-detail", + ), path( "pipelines//runs/", PipelineRunListView.as_view(),