Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ next release
---------------------

- WARNING: Vulnerablecode V1 API and UI has stopped supporting Ubuntu OVAL advisories, please shift to V3 API for new Ubuntu advisories.
- WARNING: We will deprecate improver pipelines for calculating package version rank, grouping advisories for packages and calculating risk scores in the next release, we are doing it at advisory import time instead of as separate pipelines, this will improve the performance and consistency of the data.
- Calculate package verion rank, group advisories for packages and package risk score and advisory risk score during import of advisories.
- Add attribute ``pipeline_id`` to AdvisoryV2 to track the pipeline that created the advisory, also rename existing ``datasource_id`` and AVIDs.

Version v38.6.0
Expand Down
205 changes: 158 additions & 47 deletions vulnerabilities/api_v3.py

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions vulnerabilities/improvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from vulnerabilities.improvers import vulnerability_status
from vulnerabilities.pipelines import add_cvss31_to_CVEs
from vulnerabilities.pipelines import compute_package_risk
from vulnerabilities.pipelines import compute_package_version_rank
from vulnerabilities.pipelines import enhance_with_exploitdb
from vulnerabilities.pipelines import enhance_with_kev
from vulnerabilities.pipelines import enhance_with_metasploit
Expand All @@ -32,7 +31,6 @@
enhance_with_metasploit as enhance_with_metasploit_v2,
)
from vulnerabilities.pipelines.v2_improvers import flag_ghost_packages as flag_ghost_packages_v2
from vulnerabilities.pipelines.v2_improvers import group_advisories_for_packages
from vulnerabilities.pipelines.v2_improvers import reference_collect_commits
from vulnerabilities.pipelines.v2_improvers import relate_severities
from vulnerabilities.pipelines.v2_improvers import unfurl_version_range as unfurl_version_range_v2
Expand Down Expand Up @@ -61,7 +59,6 @@
enhance_with_metasploit.MetasploitImproverPipeline,
enhance_with_exploitdb.ExploitDBImproverPipeline,
compute_package_risk.ComputePackageRiskPipeline,
compute_package_version_rank.ComputeVersionRankPipeline,
add_cvss31_to_CVEs.CVEAdvisoryMappingPipeline,
remove_duplicate_advisories.RemoveDuplicateAdvisoriesPipeline,
populate_vulnerability_summary_pipeline.PopulateVulnerabilitySummariesPipeline,
Expand All @@ -75,7 +72,6 @@
collect_ssvc_trees.CollectSSVCPipeline,
relate_severities.RelateSeveritiesPipeline,
archive_urls.ArchiveImproverPipeline,
group_advisories_for_packages.GroupAdvisoriesForPackages,
compute_advisory_todo_v2.ComputeToDo,
reference_collect_commits.CollectReferencesFixCommitsPipeline,
enhance_with_github_poc.GithubPocsImproverPipeline,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Generated by Django 5.2.11 on 2026-05-26 08:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("vulnerabilities", "0132_migrate_advisoryv2_datasource_ids"),
]

operations = [
migrations.AlterField(
model_name="advisoryv2",
name="advisory_id",
field=models.CharField(
db_index=True,
help_text="An advisory is a unique vulnerability identifier in some database, such as PYSEC-2020-2233",
max_length=200,
),
),
migrations.AlterField(
model_name="advisoryv2",
name="avid",
field=models.CharField(
help_text="Unique ID for the datasource used for this advisory .e.g.: pysec_importer_v2/PYSEC-2020-2233",
max_length=250,
),
),
migrations.AlterField(
model_name="advisoryv2",
name="datasource_id",
field=models.CharField(
db_index=True,
help_text="Unique ID for the datasource used for this advisory .e.g.: nginx",
max_length=50,
),
),
migrations.AlterField(
model_name="advisoryv2",
name="pipeline_id",
field=models.CharField(
db_index=True,
help_text="Unique ID for the pipeline used for this advisory .e.g.: nginx_importer_v2",
max_length=50,
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.2.11 on 2026-05-28 13:58

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
("vulnerabilities", "0133_alter_advisoryv2_advisory_id_alter_advisoryv2_avid_and_more"),
]

operations = [
migrations.AlterUniqueTogether(
name="advisoryset",
unique_together={("package", "relation_type", "primary_advisory")},
),
]
10 changes: 6 additions & 4 deletions vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from itertools import groupby
from operator import attrgetter
from traceback import format_exc as traceback_format_exc
from typing import Dict
from typing import List
from typing import NamedTuple
from typing import Optional
Expand Down Expand Up @@ -3054,15 +3055,15 @@ class AdvisoryV2(models.Model):

# This is similar to a type or a namespace
datasource_id = models.CharField(
max_length=200,
max_length=50,
blank=False,
null=False,
db_index=True,
help_text="Unique ID for the datasource used for this advisory ." "e.g.: nginx",
)

pipeline_id = models.CharField(
max_length=200,
max_length=50,
blank=False,
null=False,
db_index=True,
Expand All @@ -3071,7 +3072,7 @@ class AdvisoryV2(models.Model):

# This is similar to a name
advisory_id = models.CharField(
max_length=500,
max_length=200,
blank=False,
null=False,
unique=False,
Expand All @@ -3081,7 +3082,7 @@ class AdvisoryV2(models.Model):
)

avid = models.CharField(
max_length=500,
max_length=250,
blank=False,
null=False,
help_text="Unique ID for the datasource used for this advisory ."
Expand Down Expand Up @@ -3872,6 +3873,7 @@ class GroupedAdvisory(NamedTuple):
weighted_severity: Optional[float]
exploitability: Optional[float]
risk_score: Optional[float]
ssvc_trees: List[Dict]


class AdvisoryPOC(models.Model):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@
# See https://aboutcode.org for more information about nexB OSS projects.
#

from typing import List

from vulnerabilities.models import AdvisoryV2
from vulnerabilities.models import Group
from vulnerabilities.models import PackageV2
from vulnerabilities.pipelines import VulnerableCodePipeline
from vulnerabilities.pipes.group_advisories import delete_and_save_advisory_set
from vulnerabilities.pipes.group_advisories import group_advisory_for_package
from vulnerabilities.utils import TYPES_WITH_MULTIPLE_IMPORTERS
from vulnerabilities.utils import merge_advisories


class GroupAdvisoriesForPackages(VulnerableCodePipeline):
"""Group advisories for packages that have multiple importers"""

pipeline_id = "group_advisories_for_packages"
run_once = True

@classmethod
def steps(cls):
Expand All @@ -33,28 +29,4 @@ def group_advisories_for_packages(self):

def group_advisoris_for_packages(logger=None):
for package in PackageV2.objects.filter(type__in=TYPES_WITH_MULTIPLE_IMPORTERS).iterator():
logger(f"Grouping advisories for package {package.purl}")
affecting_advisories = AdvisoryV2.objects.latest_affecting_advisories_for_purl(
purl=package.purl
).prefetch_related(
"aliases",
"impacted_packages__affecting_packages",
"impacted_packages__fixed_by_packages",
)

fixed_by_advisories = AdvisoryV2.objects.latest_fixed_by_advisories_for_purl(
purl=package.purl
).prefetch_related(
"aliases",
"impacted_packages__affecting_packages",
"impacted_packages__fixed_by_packages",
)

try:
affected_groups: List[Group] = merge_advisories(affecting_advisories, package)
fixed_by_groups: List[Group] = merge_advisories(fixed_by_advisories, package)
delete_and_save_advisory_set(affected_groups, package, relation="affecting")
delete_and_save_advisory_set(fixed_by_groups, package, relation="fixing")
except Exception as e:
logger(f"Failed rebuilding advisory sets for package {package.purl}: {e!r}")
continue
group_advisory_for_package(package, logger=logger)
11 changes: 11 additions & 0 deletions vulnerabilities/pipelines/v2_improvers/unfurl_version_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from traceback import format_exc as traceback_format_exc

from aboutcode.pipeline import LoopProgress
from django.db import transaction
from django.db.models import F
from django.db.models import Q
from django.utils import timezone
Expand All @@ -26,6 +27,8 @@
from vulnerabilities.models import PipelineSchedule
from vulnerabilities.pipelines import VulnerableCodePipeline
from vulnerabilities.pipes.fetchcode_utils import get_versions
from vulnerabilities.pipes.group_advisories import group_advisory_for_package
from vulnerabilities.pipes.risk_score import compute_package_risk_score
from vulnerabilities.utils import update_purl_version


Expand Down Expand Up @@ -159,6 +162,7 @@ def get_purl_versions(purl, cached_versions, logger):
return cached_versions.get(purl) or []


@transaction.atomic
def bulk_create_with_m2m(purls, impact, relation, logger):
"""Bulk create PackageV2 and also bulk populate M2M Impact and Package relationships."""
if not purls:
Expand All @@ -181,6 +185,13 @@ def bulk_create_with_m2m(purls, impact, relation, logger):
logger(f"Error creating ImpactedPackage {relation}: {e!r} \n {traceback_format_exc()}")
return 0

for pkg in affected_packages_v2:
group_advisory_for_package(pkg, logger=logger)
risk_score = compute_package_risk_score(pkg)
logger(f"Computed risk score {risk_score} for package {pkg.purl}")
pkg.risk_score = risk_score
pkg.save()

return len(relations)


Expand Down
51 changes: 51 additions & 0 deletions vulnerabilities/pipes/advisory.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
from vulnerabilities.models import VulnerabilityRelatedReference
from vulnerabilities.models import VulnerabilitySeverity
from vulnerabilities.models import Weakness
from vulnerabilities.pipes.group_advisories import group_advisory_for_package
from vulnerabilities.pipes.risk_score import compute_advisory_risk_score
from vulnerabilities.pipes.risk_score import compute_package_risk_score
from vulnerabilities.pipes.univers_utils import get_exact_purls_v2


Expand Down Expand Up @@ -362,6 +365,12 @@ def insert_advisory_v2(
if values:
getattr(advisory_obj, field_name).add(*values)

weighted_severity, exploitability, risk_score = compute_advisory_risk_score(advisory_obj)
advisory_obj.weighted_severity = round(weighted_severity, 1) if weighted_severity is not None else None
advisory_obj.exploitability = round(exploitability, 1) if exploitability is not None else None
advisory_obj.risk_score = round(risk_score, 1) if risk_score is not None else None
advisory_obj.save()

for affected_pkg in advisory.affected_packages:
impact = ImpactedPackage.objects.create(
advisory=advisory_obj,
Expand Down Expand Up @@ -390,6 +399,48 @@ def insert_advisory_v2(
impact.affecting_packages.add(*affected_packages_v2)
impact.fixed_by_packages.add(*fixed_packages_v2)

for pkg in list(affected_packages_v2):
logger(f"Grouping advisories for package: {pkg.purl}")
try:
group_advisory_for_package(
pkg,
logger=logger,
current_advisory=advisory_obj,
current_advisory_relation="affecting",
)
except Exception as e:
logger(
f"Failed to group advisories for package {pkg.purl}: {e!r} \n {traceback_format_exc()}",
level=logging.ERROR,
)
try:
risk_score = compute_package_risk_score(pkg, current_advisory_risk_score=advisory_obj.risk_score)
logger(f"Computed risk score {risk_score} for package {pkg.purl}")
pkg.risk_score = risk_score
pkg.save()
pkg.calculate_version_rank
except Exception as e:
logger(
f"Failed to compute risk score for package {pkg.purl}: {e!r} \n {traceback_format_exc()}",
level=logging.ERROR,
)

for pkg in list(fixed_packages_v2):
logger(f"Grouping advisories for package: {pkg.purl}")
try:
group_advisory_for_package(
pkg,
logger=logger,
current_advisory=advisory_obj,
current_advisory_relation="fixing",
)
except Exception as e:
logger(
f"Failed to group advisories for package {pkg.purl}: {e!r} \n {traceback_format_exc()}",
level=logging.ERROR,
)
pkg.calculate_version_rank

introduced_commit_v2 = get_or_create_advisory_package_commit_patches(
affected_pkg.introduced_by_commit_patches
)
Expand Down
Loading
Loading