Source code for swh.osv.luigi.vuln_reports

# Copyright (C) 2026  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information

"""
Luigi tasks for downloading and processing vulnerability reports
================================================================
"""

from os import PathLike
from pathlib import Path
from typing import Dict, Iterator, Tuple

import luigi

from swh.graph.luigi.compressed_graph import LocalGraph

from .utils import _BaseUploadToS3

ALL_ZIP_URL = "https://osv-vulnerabilities.storage.googleapis.com/all.zip"


[docs] class DownloadNvdTarballs(luigi.Task): """Downloads NVD tarballs from Google Cloud Storage.""" vulnerabilities_dir = luigi.PathParameter()
[docs] def output(self) -> luigi.LocalTarget: """``nvd_cve/.stamp`` file indicating NVD tarballs have been downloaded.""" return luigi.LocalTarget(self.vulnerabilities_dir / "nvd_cve" / ".stamp")
[docs] def run(self) -> None: """Downloads NVD tarballs from GCS and creates a stamp file.""" from ..download_nvd_osv import download_to_tarballs self.vulnerabilities_dir.mkdir(exist_ok=True, parents=True) nvd_dir = self.vulnerabilities_dir / "nvd_cve" download_to_tarballs(nvd_dir) Path(self.output().path).touch()
[docs] class DownloadOsvZip(luigi.Task): """Downloads the all.zip file from the OSV vulnerability feed.""" vulnerabilities_dir = luigi.PathParameter()
[docs] def output(self) -> luigi.LocalTarget: """``all.zip`` file containing all OSV vulnerabilities.""" return luigi.LocalTarget(self.vulnerabilities_dir / "all.zip")
[docs] def run(self) -> None: """Downloads all.zip from the OSV vulnerability feed.""" import urllib.request self.vulnerabilities_dir.mkdir(exist_ok=True, parents=True) tmp_path = self.vulnerabilities_dir / "all.zip.tmp" urllib.request.urlretrieve(ALL_ZIP_URL, tmp_path) tmp_path.rename(self.output().path)
[docs] class DownloadVulnerabilities(luigi.Task): """Downloads vulnerability reports and imports them into a SQLite database.""" local_graph_path = luigi.PathParameter() graph_name = luigi.StrParameter(default="graph") vulnerabilities_dir = luigi.PathParameter()
[docs] def requires(self) -> Dict[str, luigi.Task]: """Returns instances of :class:`swh.graph.luigi.compressed_graph.LocalGraph`, :class:`DownloadNvdTarballs` and :class:`DownloadOsvZip`.""" return { "graph": LocalGraph(local_graph_path=self.local_graph_path), "nvd": DownloadNvdTarballs( vulnerabilities_dir=self.vulnerabilities_dir, ), "all_zip": DownloadOsvZip( vulnerabilities_dir=self.vulnerabilities_dir, ), }
[docs] def output(self) -> luigi.LocalTarget: """``all.sqlite`` file containing all vulnerability data.""" return luigi.LocalTarget(self.vulnerabilities_dir / "all.sqlite")
[docs] def run(self) -> None: """Downloads vulnerability reports and imports them into a SQLite database.""" import sqlite3 from ..to_sqlite import import_osv_tarballs, import_osv_zip, init_db nvd_dir = Path(self.input()["nvd"].path).parent all_zip_path = Path(self.input()["all_zip"].path) tmp_path = self.vulnerabilities_dir / "all.sqlite.tmp" with sqlite3.connect(tmp_path) as conn: init_db(conn) import_osv_tarballs(conn, nvd_dir) import_osv_zip(conn, all_zip_path) tmp_path.rename(self.output().path)
[docs] class UploadVulnerabilitiesSqliteToS3(_BaseUploadToS3): """Uploads the vulnerabilities SQLite database to S3."""
[docs] def requires(self) -> Dict[str, luigi.Task]: """Returns an instance of :class:`DownloadVulnerabilities`.""" return { "sqlite": DownloadVulnerabilities( local_graph_path=self.local_graph_path, graph_name=self.graph_name, vulnerabilities_dir=self.vulnerabilities_dir, ), }
[docs] def output(self) -> luigi.Target: """S3 target for the uploaded all.sqlite file.""" import luigi.contrib.s3 return luigi.contrib.s3.S3Target(f"{self._s3_prefix()}/all.sqlite")
def _files_to_upload(self) -> Iterator[Tuple[PathLike, str]]: """Yields (local_path, s3_key) pairs for the sqlite database.""" yield (self.input()["sqlite"].path, "all.sqlite")