diff --git a/PIPELINES-AVID.rst b/PIPELINES-AVID.rst
index 7c1ec7948..de6f399d5 100644
--- a/PIPELINES-AVID.rst
+++ b/PIPELINES-AVID.rst
@@ -96,9 +96,6 @@ The following table describes how each pipeline constructs its AVID.
* - ruby_importer_v2
- ruby_advisory_db
- {file_id}
- * - epss_importer_v2
- - epss
- - CVE ID of the record
* - gentoo_importer_v2
- gentoo
- GLSA ID of the record
diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py
index a33e6d714..1774b1150 100644
--- a/vulnerabilities/importers/__init__.py
+++ b/vulnerabilities/importers/__init__.py
@@ -20,7 +20,6 @@
from vulnerabilities.pipelines.v2_importers import (
elixir_security_importer as elixir_security_importer_v2,
)
-from vulnerabilities.pipelines.v2_importers import epss_importer_v2
from vulnerabilities.pipelines.v2_importers import fireeye_importer_v2
from vulnerabilities.pipelines.v2_importers import gentoo_importer as gentoo_importer_v2
from vulnerabilities.pipelines.v2_importers import github_osv_importer as github_osv_importer_v2
@@ -77,7 +76,6 @@
project_kb_statements_importer_v2.ProjectKBStatementsPipeline,
project_kb_msr2019_importer_v2.ProjectKBMSR2019Pipeline,
ruby_importer_v2.RubyImporterPipeline,
- epss_importer_v2.EPSSImporterPipeline,
gentoo_importer_v2.GentooImporterPipeline,
nginx_importer_v2.NginxImporterPipeline,
debian_importer_v2.DebianImporterPipeline,
diff --git a/vulnerabilities/importers/epss.py b/vulnerabilities/importers/epss.py
deleted file mode 100644
index 982229e09..000000000
--- a/vulnerabilities/importers/epss.py
+++ /dev/null
@@ -1,67 +0,0 @@
-#
-# Copyright (c) nexB Inc. and others. All rights reserved.
-# VulnerableCode is a trademark of nexB Inc.
-# SPDX-License-Identifier: Apache-2.0
-# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
-# See https://github.com/aboutcode-org/vulnerablecode for support or download.
-# See https://aboutcode.org for more information about nexB OSS projects.
-#
-import csv
-import gzip
-import logging
-import urllib.request
-from datetime import datetime
-from typing import Iterable
-
-from vulnerabilities import severity_systems
-from vulnerabilities.importer import AdvisoryData
-from vulnerabilities.importer import Importer
-from vulnerabilities.importer import Reference
-from vulnerabilities.importer import VulnerabilitySeverity
-
-logger = logging.getLogger(__name__)
-
-
-class EPSSImporter(Importer):
- """Exploit Prediction Scoring System (EPSS) Importer"""
-
- advisory_url = "https://epss.cyentia.com/epss_scores-current.csv.gz"
- spdx_license_expression = "unknown"
- importer_name = "EPSS Importer"
-
- def advisory_data(self) -> Iterable[AdvisoryData]:
- response = urllib.request.urlopen(self.advisory_url)
- with gzip.open(response, "rb") as f:
- lines = [l.decode("utf-8") for l in f.readlines()]
-
- epss_reader = csv.reader(lines)
- model_version, score_date = next(
- epss_reader
- ) # score_date='score_date:2024-05-19T00:00:00+0000'
- published_at = datetime.strptime(score_date[11::], "%Y-%m-%dT%H:%M:%S%z")
-
- next(epss_reader) # skip the header row
- for epss_row in epss_reader:
- cve, score, percentile = epss_row
-
- if not cve or not score or not percentile:
- logger.error(f"Invalid epss row: {epss_row}")
- continue
-
- severity = VulnerabilitySeverity(
- system=severity_systems.EPSS,
- value=score,
- scoring_elements=percentile,
- published_at=published_at,
- )
-
- references = Reference(
- url=f"https://api.first.org/data/v1/epss?cve={cve}",
- severities=[severity],
- )
-
- yield AdvisoryData(
- aliases=[cve],
- references=[references],
- url=self.advisory_url,
- )
diff --git a/vulnerabilities/improvers/__init__.py b/vulnerabilities/improvers/__init__.py
index 51694bde9..32265526b 100644
--- a/vulnerabilities/improvers/__init__.py
+++ b/vulnerabilities/improvers/__init__.py
@@ -17,6 +17,7 @@
from vulnerabilities.pipelines.v2_improvers import (
enhance_with_metasploit as enhance_with_metasploit_v2,
)
+from vulnerabilities.pipelines.v2_improvers import epss_pipeline
from vulnerabilities.pipelines.v2_improvers import flag_ghost_packages as flag_ghost_packages_v2
from vulnerabilities.pipelines.v2_improvers import (
group_advisories_for_packages as group_advisories_for_packages_v2,
@@ -43,5 +44,6 @@
enhance_with_github_poc.GithubPocsImproverPipeline,
mark_unfurl_version_range.MarkUnfurlVersionRangePipeline,
group_advisories_for_packages_v2.GroupAdvisoriesForPackages,
+ epss_pipeline.EPSSPipeline,
]
)
diff --git a/vulnerabilities/migrations/0145_epss_score_model.py b/vulnerabilities/migrations/0145_epss_score_model.py
new file mode 100644
index 000000000..6df56d2ed
--- /dev/null
+++ b/vulnerabilities/migrations/0145_epss_score_model.py
@@ -0,0 +1,28 @@
+# Generated by Django 5.2.11 on 2026-10-28 10:46
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('vulnerabilities', '0144_remove_advisoryv2_advisory_latest_by_avid_idx_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='EPSSScore',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date', models.DateField(help_text='Date for which this EPSS score was published')),
+ ('score', models.DecimalField(decimal_places=6, help_text='EPSS probability score', max_digits=7)),
+ ('percentile', models.DecimalField(decimal_places=6, help_text='EPSS percentile ranking', max_digits=7)),
+ ('is_latest', models.BooleanField(default=False, help_text='Indicates whether this is the latest EPSS score')),
+ ('alias', models.ForeignKey(help_text='The CVE alias associated with this EPSS score', on_delete=django.db.models.deletion.CASCADE, related_name='epss_scores', to='vulnerabilities.advisoryalias')),
+ ],
+ options={
+ 'unique_together': {('alias', 'date')},
+ },
+ ),
+ ]
diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py
index 521f0ca6a..9e1da059d 100644
--- a/vulnerabilities/models.py
+++ b/vulnerabilities/models.py
@@ -2837,6 +2837,43 @@ def url(self):
return f"https://github.com/nodejs/security-wg/blob/main/vuln/npm/{id}.json"
+class EPSSScore(models.Model):
+ """
+ Tracks daily EPSS (Exploit Prediction Scoring System) scores for CVE aliases.
+ """
+
+ alias = models.ForeignKey(
+ AdvisoryAlias,
+ related_name="epss_scores",
+ on_delete=models.CASCADE,
+ help_text="The CVE alias associated with this EPSS score",
+ )
+
+ date = models.DateField(
+ help_text="Date for which this EPSS score was published",
+ )
+
+ score = models.DecimalField(
+ max_digits=7,
+ decimal_places=6,
+ help_text="EPSS probability score",
+ )
+
+ percentile = models.DecimalField(
+ max_digits=7,
+ decimal_places=6,
+ help_text="EPSS percentile ranking",
+ )
+
+ is_latest = models.BooleanField(
+ default=False,
+ help_text="Indicates whether this is the latest EPSS score",
+ )
+
+ class Meta:
+ unique_together = (("alias", "date"),)
+
+
class Patch(models.Model):
"""
A generic patch linked to an advisory.
diff --git a/vulnerabilities/pipelines/v2_importers/epss_importer_v2.py b/vulnerabilities/pipelines/v2_importers/epss_importer_v2.py
deleted file mode 100644
index 8f3a57d95..000000000
--- a/vulnerabilities/pipelines/v2_importers/epss_importer_v2.py
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Copyright (c) nexB Inc. and others. All rights reserved.
-# VulnerableCode is a trademark of nexB Inc.
-# SPDX-License-Identifier: Apache-2.0
-# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
-# See https://github.com/aboutcode-org/vulnerablecode for support or download.
-# See https://aboutcode.org for more information about nexB OSS projects.
-#
-import csv
-import gzip
-import logging
-import urllib.request
-from datetime import datetime
-from typing import Iterable
-
-from vulnerabilities import severity_systems
-from vulnerabilities.importer import AdvisoryDataV2
-from vulnerabilities.importer import ReferenceV2
-from vulnerabilities.importer import VulnerabilitySeverity
-from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2
-
-logger = logging.getLogger(__name__)
-
-
-class EPSSImporterPipeline(VulnerableCodeBaseImporterPipelineV2):
- """Exploit Prediction Scoring System (EPSS) Importer"""
-
- advisory_url = "https://epss.cyentia.com/epss_scores-current.csv.gz"
- pipeline_id = "epss_importer_v2"
- spdx_license_expression = "unknown"
- importer_name = "EPSS Importer"
- datasource_id = "epss"
-
- exclude_from_package_todo = True
-
- precedence = 200
-
- def advisories_count(self):
- return len(self.lines)
-
- @classmethod
- def steps(cls):
- return (
- cls.fetch_db,
- cls.collect_and_store_advisories,
- )
-
- def fetch_db(self):
- logger.info(f"Fetching EPSS database from {self.advisory_url}")
- response = urllib.request.urlopen(self.advisory_url)
- with gzip.open(response, "rb") as f:
- self.lines = [l.decode("utf-8") for l in f.readlines()]
-
- def collect_advisories(self) -> Iterable[AdvisoryDataV2]:
- if not self.lines:
- logger.error("No EPSS data loaded")
- raise ValueError("EPSS data is empty")
-
- epss_reader = csv.reader(self.lines)
- model_version, score_date = next(
- epss_reader
- ) # score_date='score_date:2024-05-19T00:00:00+0000'
- published_at = datetime.strptime(score_date[11::], "%Y-%m-%dT%H:%M:%S%z")
-
- next(epss_reader) # skip the header row
- for epss_row in epss_reader:
- cve, score, percentile = epss_row
-
- if not cve or not score or not percentile:
- logger.error(f"Invalid epss row: {epss_row}")
- continue
-
- severity = VulnerabilitySeverity(
- system=severity_systems.EPSS,
- value=score,
- scoring_elements=percentile,
- published_at=published_at,
- )
-
- references = ReferenceV2(
- url=f"https://api.first.org/data/v1/epss?cve={cve}",
- )
-
- yield AdvisoryDataV2(
- advisory_id=cve,
- severities=[severity],
- references=[references],
- url=self.advisory_url,
- original_advisory_text=",".join(epss_row),
- )
diff --git a/vulnerabilities/pipelines/v2_improvers/epss_pipeline.py b/vulnerabilities/pipelines/v2_improvers/epss_pipeline.py
new file mode 100644
index 000000000..f616d037e
--- /dev/null
+++ b/vulnerabilities/pipelines/v2_improvers/epss_pipeline.py
@@ -0,0 +1,127 @@
+#
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# VulnerableCode is a trademark of nexB Inc.
+# SPDX-License-Identifier: Apache-2.0
+# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
+# See https://github.com/aboutcode-org/vulnerablecode for support or download.
+# See https://aboutcode.org for more information about nexB OSS projects.
+#
+
+import csv
+import gzip
+import urllib.request
+from datetime import datetime
+from datetime import timedelta
+from decimal import Decimal
+
+from django.utils import timezone
+
+from vulnerabilities.models import AdvisoryAlias
+from vulnerabilities.models import EPSSScore
+from vulnerabilities.models import PipelineSchedule
+from vulnerabilities.pipelines import VulnerableCodePipeline
+
+
+class EPSSPipeline(VulnerableCodePipeline):
+ """
+ Exploit Prediction Scoring System (EPSS) Pipeline.
+
+ Downloads the latest daily EPSS CSV and bulk-inserts scores into EPSSScore model. Prunes scores older than the retention window.
+ """
+
+ pipeline_id = "epss_pipeline"
+ advisory_url = "https://epss.cyentia.com/epss_scores-current.csv.gz"
+ spdx_license_expression = "unknown"
+
+ # Run pipeline every day.
+ run_interval = 1440
+ run_priority = PipelineSchedule.ExecutionPriority.HIGH
+ retention_days = 90 # Keep 90 days of EPSS scores
+ batch_size = 10_000
+
+ @classmethod
+ def steps(cls):
+ return (
+ cls.fetch_and_bulk_load_scores,
+ cls.prune_old_scores,
+ )
+
+ def fetch_and_bulk_load_scores(self):
+ self.log(f"Fetching EPSS data from {self.advisory_url}")
+ response = urllib.request.urlopen(self.advisory_url)
+
+ with gzip.open(response, "rt", encoding="utf-8") as gzip_stream:
+ epss_csv_reader = csv.reader(gzip_stream)
+
+ # Extract date from metadata header
+ _, date_header = next(epss_csv_reader)
+ score_date = datetime.fromisoformat(date_header.removeprefix("score_date:")).date()
+
+ # Skip column headers
+ next(epss_csv_reader)
+
+ scores_by_cve = {
+ cve: (epss_score, percentile) for cve, epss_score, percentile in epss_csv_reader
+ }
+
+ self.log(f"Parsed {len(scores_by_cve):,d} EPSS scores for {score_date}.")
+
+ # Map CVEs to ``AdvisoryAlias`` IDs and create any missing aliases
+ cve_to_alias_id = dict(
+ AdvisoryAlias.objects.filter(alias__in=scores_by_cve.keys()).values_list("alias", "id")
+ )
+ missing_cves = set(scores_by_cve) - set(cve_to_alias_id)
+ if missing_cves:
+ self.log(f"Creating {len(missing_cves):,d} new CVE records")
+ new_aliases = [AdvisoryAlias(alias=cve) for cve in missing_cves]
+ AdvisoryAlias.objects.bulk_create(
+ new_aliases,
+ batch_size=self.batch_size,
+ ignore_conflicts=True,
+ )
+ cve_to_alias_id.update(
+ AdvisoryAlias.objects.filter(alias__in=missing_cves).values_list("alias", "id")
+ )
+
+ # Mark previous days' scores as no longer latest ie ``is_latest=False``
+ alias_ids = list(cve_to_alias_id.values())
+ for i in range(0, len(alias_ids), self.batch_size):
+ alias_id_batch = alias_ids[i : i + self.batch_size]
+ EPSSScore.objects.filter(
+ alias_id__in=alias_id_batch,
+ date__lt=score_date,
+ is_latest=True,
+ ).update(is_latest=False)
+
+ # Bulk insert today's scores as ``is_latest=True``
+ scores = []
+ for cve, (epss_score, percentile) in scores_by_cve.items():
+ alias_id = cve_to_alias_id.get(cve)
+ if alias_id:
+ score = EPSSScore(
+ alias_id=alias_id,
+ date=score_date,
+ score=Decimal(epss_score),
+ percentile=Decimal(percentile),
+ is_latest=True,
+ )
+ scores.append(score)
+ EPSSScore.objects.bulk_create(
+ scores,
+ batch_size=self.batch_size,
+ update_conflicts=True,
+ update_fields=["score", "percentile", "is_latest"],
+ unique_fields=["alias", "date"],
+ )
+
+ self.log(f"Successfully inserted {len(scores):,d} EPSS scores for {score_date}")
+
+ def prune_old_scores(self):
+ """
+ Delete EPSS score records older than ``retention_days``.
+ """
+ cutoff = timezone.now().date() - timedelta(days=self.retention_days)
+ deleted_count, _ = EPSSScore.objects.filter(date__lt=cutoff).delete()
+ self.log(
+ f"Pruned {deleted_count:,d} EPSS records older than {cutoff} (retention: {self.retention_days} days)"
+ )
diff --git a/vulnerabilities/pipelines/v2_improvers/relate_severities.py b/vulnerabilities/pipelines/v2_improvers/relate_severities.py
index 1fa8018cc..11216b1ef 100644
--- a/vulnerabilities/pipelines/v2_improvers/relate_severities.py
+++ b/vulnerabilities/pipelines/v2_improvers/relate_severities.py
@@ -15,7 +15,6 @@
from vulnerabilities.models import AdvisoryV2
from vulnerabilities.models import PipelineSchedule
from vulnerabilities.pipelines import VulnerableCodePipeline
-from vulnerabilities.pipelines.v2_importers.epss_importer_v2 import EPSSImporterPipeline
from vulnerabilities.pipelines.v2_importers.suse_score_importer import (
SUSESeverityScoreImporterPipeline,
)
@@ -23,14 +22,13 @@
from vulnerabilities.severity_systems import CVSSV3
from vulnerabilities.severity_systems import CVSSV4
from vulnerabilities.severity_systems import CVSSV31
-from vulnerabilities.severity_systems import EPSS
logger = logging.getLogger(__name__)
class RelateSeveritiesPipeline(VulnerableCodePipeline):
"""
- Severity Relations Pipeline: Relate EPSS and SUSE CVSS severities to advisories
+ Severity Relations Pipeline: Relate SUSE CVSS severities to advisories
by matching severity advisory IDs with advisory IDs and aliases.
"""
@@ -42,7 +40,6 @@ class RelateSeveritiesPipeline(VulnerableCodePipeline):
# Severity systems to process
SUPPORTED_SYSTEMS = {
- EPSS.identifier,
CVSSV2.identifier,
CVSSV3.identifier,
CVSSV31.identifier,
@@ -50,7 +47,6 @@ class RelateSeveritiesPipeline(VulnerableCodePipeline):
}
pipelines = [
- EPSSImporterPipeline.pipeline_id,
SUSESeverityScoreImporterPipeline.pipeline_id,
]
@@ -60,7 +56,7 @@ def steps(cls):
def relate_severities(self):
"""
- Relate EPSS and SUSE severities to advisories by matching advisory IDs.
+ Relate SUSE severities to advisories by matching advisory IDs.
"""
# Filter severities by supported scoring systems
severity_score_advisories = (
diff --git a/vulnerabilities/templates/advisory_detail.html b/vulnerabilities/templates/advisory_detail.html
index fea5afde4..583fa52e2 100644
--- a/vulnerabilities/templates/advisory_detail.html
+++ b/vulnerabilities/templates/advisory_detail.html
@@ -505,17 +505,6 @@
{{ epss_data.source }} |
{% endif %}
- {% if epss_data.advisory %}
-
- |
-
- Advisory
-
- |
- {{ epss_data.advisory.avid }} |
-
- {% endif %}
{% else %}
diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_epss_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_epss_importer_v2.py
deleted file mode 100644
index bd463edf1..000000000
--- a/vulnerabilities/tests/pipelines/v2_importers/test_epss_importer_v2.py
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Copyright (c) nexB Inc. and others. All rights reserved.
-# VulnerableCode is a trademark of nexB Inc.
-# SPDX-License-Identifier: Apache-2.0
-# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
-# See https://github.com/aboutcode-org/vulnerablecode for support or download.
-# See https://aboutcode.org for more information about nexB OSS projects.
-#
-
-from pathlib import Path
-
-import pytest
-
-from vulnerabilities.pipelines.v2_importers.epss_importer_v2 import EPSSImporterPipeline
-from vulnerabilities.tests import util_tests
-
-TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "epss"
-
-TEST_CVE_FILES = (TEST_DATA / "epss_scores-2025-x-x.csv",)
-
-
-@pytest.mark.django_db
-@pytest.mark.parametrize("csv_file", TEST_CVE_FILES)
-def test_epss_advisories_per_file(csv_file):
- pipeline = EPSSImporterPipeline()
-
- with open(csv_file, "r") as f:
- pipeline.lines = f.readlines()
-
- result = [adv.to_dict() for adv in pipeline.collect_advisories()]
- expected_file = Path(TEST_DATA / "epss-expected.json")
- util_tests.check_results_against_json(result, expected_file)
diff --git a/vulnerabilities/tests/pipelines/v2_improvers/test_epss_pipeline.py b/vulnerabilities/tests/pipelines/v2_improvers/test_epss_pipeline.py
new file mode 100644
index 000000000..99ebef47c
--- /dev/null
+++ b/vulnerabilities/tests/pipelines/v2_improvers/test_epss_pipeline.py
@@ -0,0 +1,72 @@
+#
+# Copyright (c) nexB Inc. and others. All rights reserved.
+# VulnerableCode is a trademark of nexB Inc.
+# SPDX-License-Identifier: Apache-2.0
+# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
+# See https://github.com/aboutcode-org/vulnerablecode for support or download.
+# See https://aboutcode.org for more information about nexB OSS projects.
+#
+
+from datetime import timedelta
+from decimal import Decimal
+from pathlib import Path
+from unittest import mock
+
+import pytest
+from django.utils import timezone
+
+from vulnerabilities.models import AdvisoryAlias
+from vulnerabilities.models import EPSSScore
+from vulnerabilities.pipelines.v2_improvers.epss_pipeline import EPSSPipeline
+from vulnerabilities.tests import util_tests
+
+TEST_DATA = Path(__file__).resolve().parent.parent.parent / "test_data" / "epss"
+
+
+@pytest.mark.django_db
+@mock.patch("urllib.request.urlopen")
+def test_epss_pipeline(mock_urlopen):
+ test_file = TEST_DATA / "epss_scores_current.csv.gz"
+ with open(test_file, "rb") as f:
+ mock_urlopen.return_value = f
+ pipeline = EPSSPipeline()
+ pipeline.execute()
+
+ results = [
+ {
+ "cve": score.alias.alias,
+ "date": score.date.isoformat(),
+ "is_latest": score.is_latest,
+ "percentile": f"{score.percentile:.5f}",
+ "score": f"{score.score:.5f}",
+ }
+ for score in EPSSScore.objects.select_related("alias").order_by("alias__alias")
+ ]
+ expected_file = TEST_DATA / "epss_scores_expected.json"
+ util_tests.check_results_against_json(results, expected_file)
+
+
+@pytest.mark.django_db
+def test_epss_pipeline_prune_old_scores():
+ alias = AdvisoryAlias.objects.create(alias="CVE-2021-25665")
+ today = timezone.now().date()
+
+ recent = EPSSScore.objects.create(
+ alias=alias,
+ date=today - timedelta(days=30),
+ score=Decimal("0.00405"),
+ percentile=Decimal("0.61137"),
+ is_latest=True,
+ )
+ old = EPSSScore.objects.create(
+ alias=alias,
+ date=today - timedelta(days=95),
+ score=Decimal("0.00300"),
+ percentile=Decimal("0.50000"),
+ is_latest=False,
+ )
+
+ EPSSPipeline().prune_old_scores()
+
+ assert EPSSScore.objects.filter(id=recent.id).exists()
+ assert not EPSSScore.objects.filter(id=old.id).exists()
diff --git a/vulnerabilities/tests/pipelines/v2_improvers/test_relate_severities.py b/vulnerabilities/tests/pipelines/v2_improvers/test_relate_severities.py
index 04a730d70..4a2aebe9b 100644
--- a/vulnerabilities/tests/pipelines/v2_improvers/test_relate_severities.py
+++ b/vulnerabilities/tests/pipelines/v2_improvers/test_relate_severities.py
@@ -12,8 +12,11 @@
from vulnerabilities.models import AdvisorySeverity
from vulnerabilities.models import AdvisoryV2
+from vulnerabilities.pipelines.v2_importers.suse_score_importer import (
+ SUSESeverityScoreImporterPipeline,
+)
from vulnerabilities.pipelines.v2_improvers.relate_severities import RelateSeveritiesPipeline
-from vulnerabilities.severity_systems import EPSS
+from vulnerabilities.severity_systems import CVSSV3
@pytest.mark.django_db
@@ -31,17 +34,17 @@ def test_relate_severities_by_advisory_id():
severity_advisory = AdvisoryV2.objects.create(
advisory_id="CVE-2024-0001",
- datasource_id="epss",
- avid="epss/CVE-2024-0001",
+ datasource_id="suse",
+ avid="suse/CVE-2024-0001",
unique_content_id="ab2",
- url="https://example.com/epss/CVE-2024-0001",
+ url="https://example.com/suse/CVE-2024-0001",
date_collected="2024-01-02",
is_latest=True,
- pipeline_id="epss_importer_v2",
+ pipeline_id=SUSESeverityScoreImporterPipeline.pipeline_id,
)
severity_advisory.severities.create(
- scoring_system=EPSS.identifier,
- value="0.5",
+ scoring_system=CVSSV3.identifier,
+ value="5.5",
)
pipeline = RelateSeveritiesPipeline()
@@ -71,17 +74,17 @@ def test_relate_severities_via_alias():
severity_advisory = AdvisoryV2.objects.create(
advisory_id="CVE-2024-ALIAS",
- datasource_id="epss",
- avid="epss/CVE-2024-ALIAS",
+ datasource_id="suse",
+ avid="suse/CVE-2024-ALIAS",
unique_content_id="ab4",
- url="https://example.com/epss/CVE-2024-ALIAS",
+ url="https://example.com/suse/CVE-2024-ALIAS",
date_collected="2024-01-02",
is_latest=True,
- pipeline_id="epss_importer_v2",
+ pipeline_id=SUSESeverityScoreImporterPipeline.pipeline_id,
)
severity_advisory.severities.create(
- scoring_system=EPSS.identifier,
- value="0.8",
+ scoring_system=CVSSV3.identifier,
+ value="8.8",
)
pipeline = RelateSeveritiesPipeline()
@@ -94,17 +97,17 @@ def test_relate_severities_via_alias():
def test_no_self_relation_created():
advisory = AdvisoryV2.objects.create(
advisory_id="CVE-2024-0003",
- datasource_id="epss",
+ datasource_id="suse",
unique_content_id="ab5",
url="https://example.com/advisory/CVE-2024-0003",
date_collected="2024-01-03",
- avid="epss/CVE-2024-0003",
+ avid="suse/CVE-2024-0003",
is_latest=True,
- pipeline_id="epss_importer_v2",
+ pipeline_id=SUSESeverityScoreImporterPipeline.pipeline_id,
)
advisory.severities.create(
- scoring_system=EPSS.identifier,
- value="0.2",
+ scoring_system=CVSSV3.identifier,
+ value="2.2",
)
pipeline = RelateSeveritiesPipeline()
@@ -128,13 +131,13 @@ def test_unsupported_severity_system_is_ignored():
severity_advisory = AdvisoryV2.objects.create(
advisory_id="CVE-2024-0004",
- datasource_id="epss",
+ datasource_id="suse",
unique_content_id="ab7",
- url="https://example.com/epss/CVE-2024-0004",
+ url="https://example.com/suse/CVE-2024-0004",
date_collected="2024-01-02",
- avid="epss/CVE-2024-0004",
+ avid="suse/CVE-2024-0004",
is_latest=True,
- pipeline_id="epss_importer_v2",
+ pipeline_id=SUSESeverityScoreImporterPipeline.pipeline_id,
)
severity_advisory.severities.create(
scoring_system="UNKNOWN_SYSTEM",
@@ -162,17 +165,17 @@ def test_pipeline_is_idempotent():
severity = AdvisoryV2.objects.create(
advisory_id="CVE-2024-0005",
- datasource_id="epss",
+ datasource_id="suse",
unique_content_id="ab9",
- url="https://example.com/epss/CVE-2024-0005",
+ url="https://example.com/suse/CVE-2024-0005",
date_collected="2024-01-02",
is_latest=True,
- avid="epss/CVE-2024-0005",
- pipeline_id="epss_importer_v2",
+ avid="suse/CVE-2024-0005",
+ pipeline_id=SUSESeverityScoreImporterPipeline.pipeline_id,
)
severity.severities.create(
- scoring_system=EPSS.identifier,
- value="0.9",
+ scoring_system=CVSSV3.identifier,
+ value="9.0",
)
pipeline = RelateSeveritiesPipeline()
diff --git a/vulnerabilities/tests/test_data/epss/epss-expected.json b/vulnerabilities/tests/test_data/epss/epss-expected.json
deleted file mode 100644
index a690e59ad..000000000
--- a/vulnerabilities/tests/test_data/epss/epss-expected.json
+++ /dev/null
@@ -1,677 +0,0 @@
-[
- {
- "advisory_id": "CVE-1999-0001",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-1999-0001"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.01151",
- "scoring_elements": "0.7802",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-1999-0002",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-1999-0002"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.10546",
- "scoring_elements": "0.93015",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-1999-0003",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-1999-0003"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.89352",
- "scoring_elements": "0.99521",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-1999-0004",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-1999-0004"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.03037",
- "scoring_elements": "0.86249",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9970",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9970"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "7e-05",
- "scoring_elements": "0.00428",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9971",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9971"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.0024",
- "scoring_elements": "0.47036",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9972",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9972"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00543",
- "scoring_elements": "0.67059",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9975",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9975"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00039",
- "scoring_elements": "0.11657",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9976",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9976"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00085",
- "scoring_elements": "0.25184",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9977",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9977"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.06195",
- "scoring_elements": "0.90539",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9978",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9978"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00045",
- "scoring_elements": "0.13792",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9979",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9979"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00037",
- "scoring_elements": "0.10925",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9980",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9980"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00031",
- "scoring_elements": "0.08656",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9981",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9981"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00031",
- "scoring_elements": "0.08656",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9982",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9982"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00053",
- "scoring_elements": "0.16926",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9983",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9983"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00023",
- "scoring_elements": "0.05543",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9984",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9984"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00055",
- "scoring_elements": "0.17352",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9985",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9985"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.01936",
- "scoring_elements": "0.82968",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9990",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9990"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00343",
- "scoring_elements": "0.56418",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9991",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9991"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00343",
- "scoring_elements": "0.56418",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9992",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9992"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00043",
- "scoring_elements": "0.13241",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9993",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9993"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00129",
- "scoring_elements": "0.33233",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9994",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9994"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00114",
- "scoring_elements": "0.30855",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9996",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9996"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00241",
- "scoring_elements": "0.47334",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9997",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9997"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00245",
- "scoring_elements": "0.47699",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9998",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9998"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00022",
- "scoring_elements": "0.05357",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- },
- {
- "advisory_id": "CVE-2025-9999",
- "aliases": [],
- "summary": "",
- "affected_packages": [],
- "references": [
- {
- "reference_id": "",
- "reference_type": "",
- "url": "https://api.first.org/data/v1/epss?cve=CVE-2025-9999"
- }
- ],
- "patches": [],
- "severities": [
- {
- "system": "epss",
- "value": "0.00044",
- "scoring_elements": "0.13444",
- "published_at": "2025-12-26T12:55:00+00:00"
- }
- ],
- "date_published": null,
- "weaknesses": [],
- "url": "https://epss.cyentia.com/epss_scores-current.csv.gz"
- }
-]
\ No newline at end of file
diff --git a/vulnerabilities/tests/test_data/epss/epss_scores-2025-x-x.csv b/vulnerabilities/tests/test_data/epss/epss_scores-2025-x-x.csv
deleted file mode 100644
index fd2a04010..000000000
--- a/vulnerabilities/tests/test_data/epss/epss_scores-2025-x-x.csv
+++ /dev/null
@@ -1,29 +0,0 @@
-#model_version:v2025.03.14,score_date:2025-12-26T12:55:00Z
-cve,epss,percentile
-CVE-1999-0001,0.01151,0.7802
-CVE-1999-0002,0.10546,0.93015
-CVE-1999-0003,0.89352,0.99521
-CVE-1999-0004,0.03037,0.86249
-CVE-2025-9970,7e-05,0.00428
-CVE-2025-9971,0.0024,0.47036
-CVE-2025-9972,0.00543,0.67059
-CVE-2025-9975,0.00039,0.11657
-CVE-2025-9976,0.00085,0.25184
-CVE-2025-9977,0.06195,0.90539
-CVE-2025-9978,0.00045,0.13792
-CVE-2025-9979,0.00037,0.10925
-CVE-2025-9980,0.00031,0.08656
-CVE-2025-9981,0.00031,0.08656
-CVE-2025-9982,0.00053,0.16926
-CVE-2025-9983,0.00023,0.05543
-CVE-2025-9984,0.00055,0.17352
-CVE-2025-9985,0.01936,0.82968
-CVE-2025-9990,0.00343,0.56418
-CVE-2025-9991,0.00343,0.56418
-CVE-2025-9992,0.00043,0.13241
-CVE-2025-9993,0.00129,0.33233
-CVE-2025-9994,0.00114,0.30855
-CVE-2025-9996,0.00241,0.47334
-CVE-2025-9997,0.00245,0.47699
-CVE-2025-9998,0.00022,0.05357
-CVE-2025-9999,0.00044,0.13444
\ No newline at end of file
diff --git a/vulnerabilities/tests/test_data/epss/epss_scores_current.csv.gz b/vulnerabilities/tests/test_data/epss/epss_scores_current.csv.gz
new file mode 100644
index 000000000..e8882045c
Binary files /dev/null and b/vulnerabilities/tests/test_data/epss/epss_scores_current.csv.gz differ
diff --git a/vulnerabilities/tests/test_data/epss/epss_scores_expected.json b/vulnerabilities/tests/test_data/epss/epss_scores_expected.json
new file mode 100644
index 000000000..fac5e118e
--- /dev/null
+++ b/vulnerabilities/tests/test_data/epss/epss_scores_expected.json
@@ -0,0 +1,79 @@
+[
+ {
+ "cve": "CVE-2021-25665",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.61137",
+ "score": "0.00405"
+ },
+ {
+ "cve": "CVE-2021-25666",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.21821",
+ "score": "0.00072"
+ },
+ {
+ "cve": "CVE-2021-25667",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.53835",
+ "score": "0.00305"
+ },
+ {
+ "cve": "CVE-2021-25668",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.79860",
+ "score": "0.01284"
+ },
+ {
+ "cve": "CVE-2021-25669",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.82606",
+ "score": "0.01715"
+ },
+ {
+ "cve": "CVE-2021-25670",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.58798",
+ "score": "0.00367"
+ },
+ {
+ "cve": "CVE-2021-25671",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.27770",
+ "score": "0.00104"
+ },
+ {
+ "cve": "CVE-2021-25672",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.56506",
+ "score": "0.00336"
+ },
+ {
+ "cve": "CVE-2021-25673",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.12359",
+ "score": "0.00040"
+ },
+ {
+ "cve": "CVE-2021-25674",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.13878",
+ "score": "0.00045"
+ },
+ {
+ "cve": "CVE-2021-25675",
+ "date": "2026-10-28",
+ "is_latest": true,
+ "percentile": "0.13878",
+ "score": "0.00045"
+ }
+]
diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py
index b5ba74214..3acb807c3 100644
--- a/vulnerabilities/views.py
+++ b/vulnerabilities/views.py
@@ -56,7 +56,6 @@
from vulnerabilities.models import ImpactedPackageFixedBy
from vulnerabilities.models import PipelineRun
from vulnerabilities.models import PipelineSchedule
-from vulnerabilities.pipelines.v2_importers.epss_importer_v2 import EPSSImporterPipeline
from vulnerabilities.severity_systems import EPSS
from vulnerabilities.severity_systems import SCORING_SYSTEMS
from vulnerabilities.tasks import compute_queue_load_factor
@@ -582,6 +581,35 @@ def get_context_data(self, **kwargs):
return context
+def get_epss_data_for_advisory(advisory):
+ """
+ Return the latest EPSS score data mapping for the given ``advisory``,
+ or None if no score is found.
+ """
+ cve_id = advisory.advisory_id if advisory.advisory_id.startswith("CVE-") else None
+
+ # Resolve latest EPSS score via linked AdvisoryAlias records or directly by ``advisory_id`` (when advisory_id is itself a CVE)
+ epss_filter = Q(alias__advisories=advisory)
+ if cve_id:
+ epss_filter |= Q(alias__alias=cve_id)
+
+ latest_epss = (
+ models.EPSSScore.objects.filter(epss_filter, is_latest=True)
+ .select_related("alias")
+ .order_by("-score") # Surface highest EPSS score if advisory links to multiple CVEs
+ .first()
+ )
+ if not latest_epss:
+ return None
+
+ return {
+ "percentile": f"{latest_epss.percentile:.5f}",
+ "score": f"{latest_epss.score:.5f}",
+ "published_at": latest_epss.date,
+ "source": f"https://api.first.org/data/v1/epss?cve={latest_epss.alias.alias}",
+ }
+
+
class AdvisoryDetails(VulnerableCodeDetailView):
model = models.AdvisoryV2
template_name = "advisory_detail.html"
@@ -672,28 +700,7 @@ def get_context_data(self, **kwargs):
.exclude(scoring_elements="")
)
- epss_severity = advisory.severities.filter(scoring_system="epss").first()
- epss_data = None
- epss_advisory = None
- if not epss_severity:
- epss_advisory = (
- advisory.related_advisory_severities.filter(
- datasource_id=EPSSImporterPipeline.pipeline_id
- )
- .latest_per_avid()
- .first()
- )
- if epss_advisory:
- epss_severity = epss_advisory.severities.filter(scoring_system="epss").first()
- if epss_severity:
- # If the advisory itself does not have EPSS severity, but has a related advisory with EPSS severity, we use the related advisory's EPSS severity and URL as the source of EPSS data.
- epss_data = {
- "percentile": epss_severity.scoring_elements,
- "score": epss_severity.value,
- "published_at": epss_severity.published_at,
- "source": epss_advisory.url if epss_advisory else advisory.url,
- "advisory": epss_advisory if epss_advisory else advisory,
- }
+ epss_data = get_epss_data_for_advisory(advisory)
ssvc_entries = []
seen = set()