From ba8a920da5014aea3b48d0d6f11a228d33e398e0 Mon Sep 17 00:00:00 2001 From: Michael Ehab Mikhail Date: Mon, 18 Aug 2025 15:51:20 +0300 Subject: [PATCH 1/3] Add GitHub OSV Live V2 Importer Pipeline #1904 * Add GitHub OSV Live V2 Importer * Add tests for the GitHub OSV Live V2 Importer * Tested functionally using the Live Evaluation API in #1969 Signed-off-by: Michael Ehab Mikhail --- vulnerabilities/importers/__init__.py | 10 + .../v2_importers/github_osv_live_importer.py | 185 ++++++++++++++++++ .../test_github_osv_live_importer_v2.py | 59 ++++++ 3 files changed, 254 insertions(+) create mode 100644 vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py create mode 100644 vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 439e69731..57ac3a7c4 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -55,6 +55,9 @@ 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 +from vulnerabilities.pipelines.v2_importers import ( + github_osv_live_importer as github_osv_live_importer_v2, +) from vulnerabilities.pipelines.v2_importers import gitlab_importer as gitlab_importer_v2 from vulnerabilities.pipelines.v2_importers import istio_importer as istio_importer_v2 from vulnerabilities.pipelines.v2_importers import mattermost_importer as mattermost_importer_v2 @@ -116,6 +119,7 @@ retiredotnet_importer_v2.RetireDotnetImporterPipeline, ubuntu_osv_importer_v2.UbuntuOSVImporterPipeline, alpine_linux_importer_v2.AlpineLinuxImporterPipeline, + nvd_importer.NVDImporterPipeline, github_importer.GitHubAPIImporterPipeline, gitlab_importer.GitLabImporterPipeline, github_osv.GithubOSVImporter, @@ -189,3 +193,9 @@ collect_fix_commits_v2.CollectGitlabFixCommitsPipeline, ] ) + +LIVE_IMPORTERS_REGISTRY = create_registry( + [ + github_osv_live_importer_v2.GithubOSVLiveImporterPipeline, + ] +) diff --git a/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py new file mode 100644 index 000000000..e3a4d3cbe --- /dev/null +++ b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py @@ -0,0 +1,185 @@ +import json +from typing import Iterable + +import requests +from packageurl import PackageURL +from univers.version_range import RANGE_CLASS_BY_SCHEMES + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 + + +class GithubOSVLiveImporterPipeline(VulnerableCodeBaseImporterPipelineV2): + """ + GithubOSV Live Importer Pipeline + + Collect advisories from GitHub Advisory Database for a single PURL. + """ + + pipeline_id = "github_osv_live_importer_v2" + spdx_license_expression = "CC-BY-4.0" + license_url = "https://github.com/github/advisory-database/blob/main/LICENSE.md" + supported_types = ["pypi", "npm", "maven", "composer", "hex", "gem", "nuget", "cargo"] + + @classmethod + def steps(cls): + return ( + cls.get_purl_inputs, + cls.collect_and_store_advisories, + ) + + def get_purl_inputs(self): + purl = self.inputs["purl"] + if not purl: + raise ValueError("PURL is required for GithubOSVLiveImporterPipeline") + + if isinstance(purl, str): + purl = PackageURL.from_string(purl) + + if not isinstance(purl, PackageURL): + raise ValueError(f"Object of type {type(purl)} {purl!r} is not a PackageURL instance") + + if purl.type not in self.supported_types: + raise ValueError( + f"PURL: {purl!s} is not among the supported package types {self.supported_types!r}" + ) + + if not purl.version: + raise ValueError(f"PURL: {purl!s} is expected to have a version") + + self.purl = purl + + def advisories_count(self): + self.advisories = fetch_github_osv_advisories_for_purl(self.purl) + return len(self.advisories) + + def collect_advisories(self) -> Iterable[AdvisoryData]: + from vulnerabilities.importers.osv import parse_advisory_data_v2 + + supported_ecosystems = [ + "pypi", + "npm", + "maven", + # "golang", + "composer", + "hex", + "gem", + "nuget", + "cargo", + ] + + input_version = self.purl.version + vrc = RANGE_CLASS_BY_SCHEMES[self.purl.type] + version_obj = vrc.version_class(input_version) + + for adv in self.advisories: + adv_id = adv.get("id") + advisory_url = build_github_repo_advisory_url(adv, adv_id) + + advisory = parse_advisory_data_v2( + raw_data=adv, + supported_ecosystems=supported_ecosystems, + advisory_url=advisory_url, + advisory_text=json.dumps(adv, ensure_ascii=False), + ) + + advisory.affected_packages = [ + ap + for ap in advisory.affected_packages + if ap.package + and ap.package.type == self.purl.type + and ap.package.name == self.purl.name + and (ap.package.namespace or "") == (self.purl.namespace or "") + ] + + if not advisory.affected_packages: + continue + + if any( + ap.affected_version_range and version_obj in ap.affected_version_range + for ap in advisory.affected_packages + ): + yield advisory + + +ECOSYSTEM_BY_PURL_TYPE = { + "pypi": "PyPI", + "npm": "npm", + "maven": "Maven", + "composer": "Packagist", + "hex": "Hex", + "gem": "RubyGems", + "nuget": "NuGet", + "cargo": "crates.io", +} + +# Map purl.type to directory names used in the advisory-database repository +REPO_DIR_BY_PURL_TYPE = { + "pypi": "pypi", + "npm": "npm", + "maven": "maven", + "composer": "composer", + "hex": "hex", + "gem": "rubygems", + "nuget": "nuget", + "cargo": "crates.io", +} + + +def build_github_repo_advisory_url(adv: dict, adv_id: str | None) -> str: + """ + Return the advisory JSON URL in the GitHub advisory-database repo, using the GHSA path: + advisories/github-reviewed/YYYY/MM/GHSA-ID/GHSA-ID.json + """ + base = "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed" + if not adv_id: + return f"{base}/" + + date_str = adv.get("published") or adv.get("modified") + + if date_str: + from datetime import datetime + + try: + dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) + year = dt.strftime("%Y") + month = dt.strftime("%m") + return f"{base}/{year}/{month}/{adv_id}/{adv_id}.json" + except Exception: + pass + + # Fallback to the base directory if no parseable date is present + return f"{base}/" + + +def _osv_package_name(purl: PackageURL) -> str: + # Maven uses groupId:artifactId, most others use namespace/name when namespace exists + if purl.type == "maven" and purl.namespace: + return f"{purl.namespace}:{purl.name}" + if purl.namespace: + return f"{purl.namespace}/{purl.name}" + return purl.name + + +def fetch_github_osv_advisories_for_purl(purl: PackageURL): + """ + Return a list of OSV advisory dicts from the OSV API for a given PURL, + filtered to only GitHub advisories (GHSA-*). + """ + ecosystem = ECOSYSTEM_BY_PURL_TYPE.get(purl.type) + if not ecosystem: + return [] + + pkg = {"ecosystem": ecosystem, "name": _osv_package_name(purl)} + # Query by package to get all advisories for that package; we filter GHSA below. + body = {"package": pkg} + try: + resp = requests.post("https://api.osv.dev/v1/query", json=body, timeout=30) + if resp.status_code != 200: + return [] + data = resp.json() or {} + vulns = data.get("vulns") or [] + # Keep only GHSA advisories which correspond to GitHub Advisory Database + return [v for v in vulns if isinstance(v.get("id"), str) and v["id"].startswith("GHSA-")] + except Exception: + return [] diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py new file mode 100644 index 000000000..1886fccd0 --- /dev/null +++ b/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py @@ -0,0 +1,59 @@ +import json +from unittest import mock + +from packageurl import PackageURL + +from vulnerabilities.importer import AdvisoryData +from vulnerabilities.pipelines.v2_importers.github_osv_live_importer import ( + GithubOSVLiveImporterPipeline, +) + +SAMPLE_OSV = { + "id": "GHSA-xxxx-yyyy-zzzz", + "summary": "Sample summary", + "details": "Sample details", + "aliases": ["CVE-2021-99999"], + "affected": [ + { + "package": {"name": "sample", "ecosystem": "PyPI"}, + "ranges": [ + {"type": "ECOSYSTEM", "events": [{"introduced": "1.0.0"}, {"fixed": "1.2.0"}]} + ], + "versions": ["1.0.0", "1.1.0"], + } + ], + "database_specific": {"cwe_ids": ["CWE-79"]}, +} + + +@mock.patch( + "vulnerabilities.pipelines.v2_importers.github_osv_live_importer.fetch_github_osv_advisories_for_purl" +) +def test_github_osv_live_importer_found_with_version(mock_fetch): + mock_fetch.return_value = [json.loads(json.dumps(SAMPLE_OSV))] + purl = PackageURL(type="pypi", name="sample", version="1.1.0") + pipeline = GithubOSVLiveImporterPipeline(purl=purl) + pipeline.get_purl_inputs() + pipeline.advisories_count() + advisories = list(pipeline.collect_advisories()) + assert len(advisories) == 1 + adv = advisories[0] + assert isinstance(adv, AdvisoryData) + assert adv.advisory_id == "GHSA-xxxx-yyyy-zzzz" + assert "CVE-2021-99999" in adv.aliases + assert adv.summary.startswith("Sample") + assert adv.affected_packages + assert adv.affected_packages[0].package.type == "pypi" + + +@mock.patch( + "vulnerabilities.pipelines.v2_importers.github_osv_live_importer.fetch_github_osv_advisories_for_purl" +) +def test_github_osv_live_importer_none_found_with_version(mock_fetch): + mock_fetch.return_value = [json.loads(json.dumps(SAMPLE_OSV))] + purl = PackageURL(type="pypi", name="sample", version="1.2.0") + pipeline = GithubOSVLiveImporterPipeline(purl=purl) + pipeline.get_purl_inputs() + pipeline.advisories_count() + advisories = list(pipeline.collect_advisories()) + assert advisories == [] From ec077d70f6543d42a00122adfa43ea947edf9c80 Mon Sep 17 00:00:00 2001 From: Michael Ehab Mikhail Date: Mon, 18 Aug 2025 16:17:15 +0300 Subject: [PATCH 2/3] Use Optional to support Python 3.9 #1904 Signed-off-by: Michael Ehab Mikhail --- .../pipelines/v2_importers/github_osv_live_importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py index e3a4d3cbe..14d0045fa 100644 --- a/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py +++ b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py @@ -1,5 +1,6 @@ import json from typing import Iterable +from typing import Optional import requests from packageurl import PackageURL @@ -126,7 +127,7 @@ def collect_advisories(self) -> Iterable[AdvisoryData]: } -def build_github_repo_advisory_url(adv: dict, adv_id: str | None) -> str: +def build_github_repo_advisory_url(adv: dict, adv_id: Optional[str]) -> str: """ Return the advisory JSON URL in the GitHub advisory-database repo, using the GHSA path: advisories/github-reviewed/YYYY/MM/GHSA-ID/GHSA-ID.json From 5c4696ddfa9e6f8f9daa9f14d6c6e08df2dceeb9 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Mon, 27 Apr 2026 04:26:55 +0300 Subject: [PATCH 3/3] refactor: Refactor the GithubOSVLiveImporterPipeline pipeline and add a test using real data Signed-off-by: ziad hany --- vulnerabilities/importers/__init__.py | 1 - .../v2_importers/github_osv_live_importer.py | 204 ++++----- .../test_github_osv_live_importer_v2.py | 96 ++-- .../live_github_osv/expected-advisories.json | 123 +++++ .../live_github_osv/fetch_github_osv.json | 288 ++++++++++++ .../live_github_osv/fetch_osv_api.json | 428 ++++++++++++++++++ 6 files changed, 979 insertions(+), 161 deletions(-) create mode 100644 vulnerabilities/tests/test_data/live_github_osv/expected-advisories.json create mode 100644 vulnerabilities/tests/test_data/live_github_osv/fetch_github_osv.json create mode 100644 vulnerabilities/tests/test_data/live_github_osv/fetch_osv_api.json diff --git a/vulnerabilities/importers/__init__.py b/vulnerabilities/importers/__init__.py index 57ac3a7c4..2e7cd9e3c 100644 --- a/vulnerabilities/importers/__init__.py +++ b/vulnerabilities/importers/__init__.py @@ -119,7 +119,6 @@ retiredotnet_importer_v2.RetireDotnetImporterPipeline, ubuntu_osv_importer_v2.UbuntuOSVImporterPipeline, alpine_linux_importer_v2.AlpineLinuxImporterPipeline, - nvd_importer.NVDImporterPipeline, github_importer.GitHubAPIImporterPipeline, gitlab_importer.GitLabImporterPipeline, github_osv.GithubOSVImporter, diff --git a/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py index 14d0045fa..25b15ae04 100644 --- a/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py +++ b/vulnerabilities/pipelines/v2_importers/github_osv_live_importer.py @@ -1,13 +1,24 @@ import json -from typing import Iterable -from typing import Optional +import dateparser import requests from packageurl import PackageURL from univers.version_range import RANGE_CLASS_BY_SCHEMES -from vulnerabilities.importer import AdvisoryData from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipelineV2 +from vulnerabilities.pipes.osv_v2 import parse_advisory_data_v3 +from vulnerabilities.utils import fetch_response + +ECOSYSTEM_BY_PURL_TYPE = { + "pypi": "PyPI", + "npm": "npm", + "maven": "Maven", + "composer": "Packagist", + "hex": "Hex", + "gem": "RubyGems", + "nuget": "NuGet", + "cargo": "crates.io", +} class GithubOSVLiveImporterPipeline(VulnerableCodeBaseImporterPipelineV2): @@ -26,11 +37,12 @@ class GithubOSVLiveImporterPipeline(VulnerableCodeBaseImporterPipelineV2): def steps(cls): return ( cls.get_purl_inputs, + cls.get_osv_advisories_urls, cls.collect_and_store_advisories, ) def get_purl_inputs(self): - purl = self.inputs["purl"] + purl = self.inputs.get("purl") if not purl: raise ValueError("PURL is required for GithubOSVLiveImporterPipeline") @@ -51,106 +63,94 @@ def get_purl_inputs(self): self.purl = purl def advisories_count(self): - self.advisories = fetch_github_osv_advisories_for_purl(self.purl) - return len(self.advisories) - - def collect_advisories(self) -> Iterable[AdvisoryData]: - from vulnerabilities.importers.osv import parse_advisory_data_v2 - - supported_ecosystems = [ - "pypi", - "npm", - "maven", - # "golang", - "composer", - "hex", - "gem", - "nuget", - "cargo", - ] - - input_version = self.purl.version - vrc = RANGE_CLASS_BY_SCHEMES[self.purl.type] - version_obj = vrc.version_class(input_version) - - for adv in self.advisories: - adv_id = adv.get("id") - advisory_url = build_github_repo_advisory_url(adv, adv_id) - - advisory = parse_advisory_data_v2( - raw_data=adv, - supported_ecosystems=supported_ecosystems, + return len(self.advisory_urls) + + def collect_advisories(self): + """ + Fetch and parse advisory data from GitHub Advisory Database URLs, Filters the packages to + ensure they match the exact type, name, and namespace of the target PURL, and ensure the target + version falls within the affected or fixed version ranges and yield these related advisories + """ + version_range = RANGE_CLASS_BY_SCHEMES.get(self.purl.type) + version_obj = version_range.version_class(self.purl.version) + for advisory_url in self.advisory_urls: + response = fetch_response(advisory_url) + raw_data = json.loads(response.content) + + advisory = parse_advisory_data_v3( + raw_data=raw_data, + supported_ecosystems=self.supported_types, advisory_url=advisory_url, - advisory_text=json.dumps(adv, ensure_ascii=False), + advisory_text=json.dumps(raw_data, ensure_ascii=False), ) - advisory.affected_packages = [ - ap - for ap in advisory.affected_packages - if ap.package - and ap.package.type == self.purl.type - and ap.package.name == self.purl.name - and (ap.package.namespace or "") == (self.purl.namespace or "") + filtered_affected_packages = [ + affected_package + for affected_package in advisory.affected_packages + if affected_package.package + and affected_package.package.type == self.purl.type + and affected_package.package.name == self.purl.name + and (affected_package.package.namespace or "") == (self.purl.namespace or "") ] - if not advisory.affected_packages: + if not filtered_affected_packages: continue - if any( - ap.affected_version_range and version_obj in ap.affected_version_range - for ap in advisory.affected_packages - ): - yield advisory - - -ECOSYSTEM_BY_PURL_TYPE = { - "pypi": "PyPI", - "npm": "npm", - "maven": "Maven", - "composer": "Packagist", - "hex": "Hex", - "gem": "RubyGems", - "nuget": "NuGet", - "cargo": "crates.io", -} - -# Map purl.type to directory names used in the advisory-database repository -REPO_DIR_BY_PURL_TYPE = { - "pypi": "pypi", - "npm": "npm", - "maven": "maven", - "composer": "composer", - "hex": "hex", - "gem": "rubygems", - "nuget": "nuget", - "cargo": "crates.io", -} + for affected_package in filtered_affected_packages: + if ( + affected_package.affected_version_range + and version_obj in affected_package.affected_version_range + ) or ( + affected_package.fixed_version_range + and version_obj in affected_package.fixed_version_range + ): + yield advisory + + def get_osv_advisories_urls(self): + """ + Fetch a list of OSV advisory dicts from the OSV API for a given PURL, + filtered to only GitHub advisories (GHSA-*) and return the Advisories URLS. + """ + ecosystem = ECOSYSTEM_BY_PURL_TYPE.get(self.purl.type) + if not ecosystem: + return [] + # Query by package to get all advisories for that package; we filter GHSA below. + body = {"package": {"ecosystem": ecosystem, "name": _osv_package_name(self.purl)}} + resp = requests.post("https://api.osv.dev/v1/query", json=body, timeout=30) + if resp.status_code != 200: + return [] -def build_github_repo_advisory_url(adv: dict, adv_id: Optional[str]) -> str: + data = resp.json() or {} + advisories = data.get("vulns") or [] + self.advisory_urls = set() + for advisory in advisories: + adv_id = advisory.get("id") or "" + aliases = advisory.get("aliases") or [] + advisory_ids = [adv_id] + aliases + for ghsa_id in advisory_ids: + if not ghsa_id.startswith("GHSA-"): + continue + + published_date = advisory.get("published") + advisory_url = build_github_repo_advisory_url( + published_date, ghsa_id, logger=self.log + ) + self.advisory_urls.add(advisory_url) + + +def build_github_repo_advisory_url(published_date, advisory_id, logger): """ Return the advisory JSON URL in the GitHub advisory-database repo, using the GHSA path: advisories/github-reviewed/YYYY/MM/GHSA-ID/GHSA-ID.json """ - base = "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed" - if not adv_id: - return f"{base}/" - - date_str = adv.get("published") or adv.get("modified") + if not published_date: + logger(f"Cannot build URL for {advisory_id}: Missing both published and modified dates") - if date_str: - from datetime import datetime - - try: - dt = datetime.fromisoformat(date_str.replace("Z", "+00:00")) - year = dt.strftime("%Y") - month = dt.strftime("%m") - return f"{base}/{year}/{month}/{adv_id}/{adv_id}.json" - except Exception: - pass - - # Fallback to the base directory if no parseable date is present - return f"{base}/" + parsed_date = dateparser.parse(date_string=published_date) + year = parsed_date.strftime("%Y") + month = parsed_date.strftime("%m") + return f"https://raw.githubusercontent.com/github/advisory-database/refs/heads/main/advisories/github-reviewed/{year}/{month}/{advisory_id}/{advisory_id}.json" def _osv_package_name(purl: PackageURL) -> str: @@ -160,27 +160,3 @@ def _osv_package_name(purl: PackageURL) -> str: if purl.namespace: return f"{purl.namespace}/{purl.name}" return purl.name - - -def fetch_github_osv_advisories_for_purl(purl: PackageURL): - """ - Return a list of OSV advisory dicts from the OSV API for a given PURL, - filtered to only GitHub advisories (GHSA-*). - """ - ecosystem = ECOSYSTEM_BY_PURL_TYPE.get(purl.type) - if not ecosystem: - return [] - - pkg = {"ecosystem": ecosystem, "name": _osv_package_name(purl)} - # Query by package to get all advisories for that package; we filter GHSA below. - body = {"package": pkg} - try: - resp = requests.post("https://api.osv.dev/v1/query", json=body, timeout=30) - if resp.status_code != 200: - return [] - data = resp.json() or {} - vulns = data.get("vulns") or [] - # Keep only GHSA advisories which correspond to GitHub Advisory Database - return [v for v in vulns if isinstance(v.get("id"), str) and v["id"].startswith("GHSA-")] - except Exception: - return [] diff --git a/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py b/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py index 1886fccd0..5a5950fe5 100644 --- a/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py +++ b/vulnerabilities/tests/pipelines/v2_importers/test_github_osv_live_importer_v2.py @@ -1,59 +1,63 @@ import json +from pathlib import Path from unittest import mock +import pytest from packageurl import PackageURL -from vulnerabilities.importer import AdvisoryData +from vulnerabilities.models import AdvisoryV2 from vulnerabilities.pipelines.v2_importers.github_osv_live_importer import ( GithubOSVLiveImporterPipeline, ) +from vulnerabilities.pipelines.v2_importers.github_osv_live_importer import ( + build_github_repo_advisory_url, +) +from vulnerabilities.tests import util_tests -SAMPLE_OSV = { - "id": "GHSA-xxxx-yyyy-zzzz", - "summary": "Sample summary", - "details": "Sample details", - "aliases": ["CVE-2021-99999"], - "affected": [ - { - "package": {"name": "sample", "ecosystem": "PyPI"}, - "ranges": [ - {"type": "ECOSYSTEM", "events": [{"introduced": "1.0.0"}, {"fixed": "1.2.0"}]} - ], - "versions": ["1.0.0", "1.1.0"], - } - ], - "database_specific": {"cwe_ids": ["CWE-79"]}, -} +TEST_DATA = Path(__file__).parent.parent.parent / "test_data" / "live_github_osv" -@mock.patch( - "vulnerabilities.pipelines.v2_importers.github_osv_live_importer.fetch_github_osv_advisories_for_purl" -) -def test_github_osv_live_importer_found_with_version(mock_fetch): - mock_fetch.return_value = [json.loads(json.dumps(SAMPLE_OSV))] - purl = PackageURL(type="pypi", name="sample", version="1.1.0") +@pytest.mark.django_db +@mock.patch("vulnerabilities.pipelines.v2_importers.github_osv_live_importer.fetch_response") +@mock.patch("vulnerabilities.pipelines.v2_importers.github_osv_live_importer.requests.post") +def test_github_osv_live_importer(mocker_osv, mock_github_osv): + purl = PackageURL(type="pypi", name="django", version="1.4.2") + + mocker_osv.return_value.status_code = 200 + osv_api_path = TEST_DATA / "fetch_osv_api.json" + with open(osv_api_path, encoding="utf-8") as f: + mocker_osv.return_value.json.return_value = json.load(f) + + github_osv_path = TEST_DATA / "fetch_github_osv.json" + with open(github_osv_path, encoding="utf-8") as f: + raw_advisory_list = json.load(f) + + mock_github_osv.side_effect = lambda url: mock.Mock( + content=json.dumps(next(adv for adv in raw_advisory_list if adv.get("id") in url)) + ) + pipeline = GithubOSVLiveImporterPipeline(purl=purl) - pipeline.get_purl_inputs() - pipeline.advisories_count() - advisories = list(pipeline.collect_advisories()) - assert len(advisories) == 1 - adv = advisories[0] - assert isinstance(adv, AdvisoryData) - assert adv.advisory_id == "GHSA-xxxx-yyyy-zzzz" - assert "CVE-2021-99999" in adv.aliases - assert adv.summary.startswith("Sample") - assert adv.affected_packages - assert adv.affected_packages[0].package.type == "pypi" - - -@mock.patch( - "vulnerabilities.pipelines.v2_importers.github_osv_live_importer.fetch_github_osv_advisories_for_purl" + pipeline.execute() + + expected_file = TEST_DATA / "expected-advisories.json" + result = [adv.to_advisory_data().to_dict() for adv in AdvisoryV2.objects.all()] + util_tests.check_results_against_json(result, expected_file) + + +@pytest.mark.parametrize( + "published_date, advisory_id, expected_url", + [ + ( + "2022-05-17T05:10:31Z", + "GHSA-2655-q453-22f9", + "https://raw.githubusercontent.com/github/advisory-database/refs/heads/main/advisories/github-reviewed/2022/05/GHSA-2655-q453-22f9/GHSA-2655-q453-22f9.json", + ), + ( + "2017-10-24T18:33:37Z", + "GHSA-4936-rj25-6wm6", + "https://raw.githubusercontent.com/github/advisory-database/refs/heads/main/advisories/github-reviewed/2017/10/GHSA-4936-rj25-6wm6/GHSA-4936-rj25-6wm6.json", + ), + ], ) -def test_github_osv_live_importer_none_found_with_version(mock_fetch): - mock_fetch.return_value = [json.loads(json.dumps(SAMPLE_OSV))] - purl = PackageURL(type="pypi", name="sample", version="1.2.0") - pipeline = GithubOSVLiveImporterPipeline(purl=purl) - pipeline.get_purl_inputs() - pipeline.advisories_count() - advisories = list(pipeline.collect_advisories()) - assert advisories == [] +def test_build_github_repo_advisory_url(published_date, advisory_id, expected_url): + assert build_github_repo_advisory_url(published_date, advisory_id, logger=print) == expected_url diff --git a/vulnerabilities/tests/test_data/live_github_osv/expected-advisories.json b/vulnerabilities/tests/test_data/live_github_osv/expected-advisories.json new file mode 100644 index 000000000..3242327fc --- /dev/null +++ b/vulnerabilities/tests/test_data/live_github_osv/expected-advisories.json @@ -0,0 +1,123 @@ +[ + { + "advisory_id": "GHSA-296w-6qhq-gf92", + "aliases": [ + "CVE-2014-0481" + ], + "summary": "Django denial of service via file upload naming\nThe default configuration for the file upload handling system in Django before 1.4.14, 1.5.x before 1.5.9, 1.6.x before 1.6.6, and 1.7 before release candidate 3 uses a sequential file name generation process when a file with a conflicting name is uploaded, which allows remote attackers to cause a denial of service (CPU consumption) by unloading a multiple files with the same name.", + "affected_packages": [ + { + "package": { + "type": "pypi", + "namespace": "", + "name": "django", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:pypi/<1.4.14", + "fixed_version_range": "vers:pypi/1.4.14", + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + }, + { + "package": { + "type": "pypi", + "namespace": "", + "name": "django", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:pypi/>=1.5|<1.5.9", + "fixed_version_range": "vers:pypi/1.5.9", + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + }, + { + "package": { + "type": "pypi", + "namespace": "", + "name": "django", + "version": "", + "qualifiers": "", + "subpath": "" + }, + "affected_version_range": "vers:pypi/>=1.6|<1.6.6", + "fixed_version_range": "vers:pypi/1.6.6", + "introduced_by_commit_patches": [], + "fixed_by_commit_patches": [] + } + ], + "references": [ + { + "reference_id": "", + "reference_type": "", + "url": "http://lists.opensuse.org/opensuse-updates/2014-09/msg00023.html" + }, + { + "reference_id": "", + "reference_type": "", + "url": "http://www.debian.org/security/2014/dsa-3010" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://github.com/django/django" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://github.com/django/django/commit/26cd48e166ac4d84317c8ee6d63ac52a87e8da99" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://github.com/django/django/commit/30042d475bf084c6723c6217a21598d9247a9c41" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://github.com/django/django/commit/dd0c3f4ee1a30c1a1e6055061c6ba6e58c6b54d1" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2014-5.yaml" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-0481" + }, + { + "reference_id": "", + "reference_type": "", + "url": "https://www.djangoproject.com/weblog/2014/aug/20/security" + } + ], + "patches": [], + "severities": [ + { + "system": "cvssv3.1", + "value": "7.5", + "scoring_elements": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + { + "system": "cvssv4", + "value": "8.7", + "scoring_elements": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N" + }, + { + "system": "generic_textual", + "value": "HIGH", + "scoring_elements": "" + } + ], + "date_published": "2022-05-14T02:05:08+00:00", + "weaknesses": [ + 400 + ], + "url": "https://raw.githubusercontent.com/github/advisory-database/refs/heads/main/advisories/github-reviewed/2022/05/GHSA-296w-6qhq-gf92/GHSA-296w-6qhq-gf92.json" + } +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/live_github_osv/fetch_github_osv.json b/vulnerabilities/tests/test_data/live_github_osv/fetch_github_osv.json new file mode 100644 index 000000000..5107fe597 --- /dev/null +++ b/vulnerabilities/tests/test_data/live_github_osv/fetch_github_osv.json @@ -0,0 +1,288 @@ +[ + { + "schema_version": "1.4.0", + "id": "GHSA-296w-6qhq-gf92", + "modified": "2024-09-18T15:57:34Z", + "published": "2022-05-14T02:05:08Z", + "aliases": [ + "CVE-2014-0481" + ], + "summary": "Django denial of service via file upload naming", + "details": "The default configuration for the file upload handling system in Django before 1.4.14, 1.5.x before 1.5.9, 1.6.x before 1.6.6, and 1.7 before release candidate 3 uses a sequential file name generation process when a file with a conflicting name is uploaded, which allows remote attackers to cause a denial of service (CPU consumption) by unloading a multiple files with the same name.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.4.14" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "1.5" + }, + { + "fixed": "1.5.9" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "1.6" + }, + { + "fixed": "1.6.6" + } + ] + } + ] + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-0481" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/26cd48e166ac4d84317c8ee6d63ac52a87e8da99" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/30042d475bf084c6723c6217a21598d9247a9c41" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/dd0c3f4ee1a30c1a1e6055061c6ba6e58c6b54d1" + }, + { + "type": "PACKAGE", + "url": "https://github.com/django/django" + }, + { + "type": "WEB", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2014-5.yaml" + }, + { + "type": "WEB", + "url": "https://www.djangoproject.com/weblog/2014/aug/20/security" + }, + { + "type": "WEB", + "url": "http://lists.opensuse.org/opensuse-updates/2014-09/msg00023.html" + }, + { + "type": "WEB", + "url": "http://www.debian.org/security/2014/dsa-3010" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "severity": "HIGH", + "github_reviewed": true, + "github_reviewed_at": "2023-08-15T23:28:45Z", + "nvd_published_at": "2014-08-26T14:55:00Z" + } +}, + { + "schema_version": "1.4.0", + "id": "GHSA-2gwj-7jmv-h26r", + "modified": "2024-09-20T15:09:55Z", + "published": "2022-04-13T00:00:33Z", + "aliases": [ + "CVE-2022-28346" + ], + "summary": "SQL Injection in Django", + "details": "An issue was discovered in Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4. `QuerySet.annotate()`, `aggregate()`, and `extra()` methods are subject to SQL injection in column aliases via a crafted dictionary (with dictionary expansion) as the passed `**kwargs`.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.2" + }, + { + "fixed": "2.2.28" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "3.2" + }, + { + "fixed": "3.2.13" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "PyPI", + "name": "Django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "4.0" + }, + { + "fixed": "4.0.4" + } + ] + } + ] + } + ], + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-28346" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/2044dac5c6968441be6f534c4139bcf48c5c7e48" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/2c09e68ec911919360d5f8502cefc312f9e03c5d" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/800828887a0509ad1162d6d407e94d8de7eafc60" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/93cae5cb2f9a4ef1514cf1a41f714fef08005200" + }, + { + "type": "WEB", + "url": "https://docs.djangoproject.com/en/4.0/releases/security" + }, + { + "type": "ADVISORY", + "url": "https://github.com/advisories/GHSA-2gwj-7jmv-h26r" + }, + { + "type": "PACKAGE", + "url": "https://github.com/django/django" + }, + { + "type": "WEB", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2022-190.yaml" + }, + { + "type": "WEB", + "url": "https://groups.google.com/forum/#!forum/django-announce" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00013.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IK" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQI" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20220609-0002" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5254" + }, + { + "type": "WEB", + "url": "https://www.djangoproject.com/weblog/2022/apr/11/security-releases" + }, + { + "type": "WEB", + "url": "http://www.openwall.com/lists/oss-security/2022/04/11/1" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-89" + ], + "severity": "CRITICAL", + "github_reviewed": true, + "github_reviewed_at": "2022-04-22T20:33:03Z", + "nvd_published_at": "2022-04-12T05:15:00Z" + } +} +] \ No newline at end of file diff --git a/vulnerabilities/tests/test_data/live_github_osv/fetch_osv_api.json b/vulnerabilities/tests/test_data/live_github_osv/fetch_osv_api.json new file mode 100644 index 000000000..7d4af55f4 --- /dev/null +++ b/vulnerabilities/tests/test_data/live_github_osv/fetch_osv_api.json @@ -0,0 +1,428 @@ +{ + "vulns": [ + { + "id": "GHSA-296w-6qhq-gf92", + "summary": "Django denial of service via file upload naming", + "details": "The default configuration for the file upload handling system in Django before 1.4.14, 1.5.x before 1.5.9, 1.6.x before 1.6.6, and 1.7 before release candidate 3 uses a sequential file name generation process when a file with a conflicting name is uploaded, which allows remote attackers to cause a denial of service (CPU consumption) by unloading a multiple files with the same name.", + "aliases": [ + "CVE-2014-0481", + "PYSEC-2014-5" + ], + "modified": "2025-02-19T05:35:45.525230Z", + "published": "2022-05-14T02:05:08Z", + "database_specific": { + "cwe_ids": [ + "CWE-400" + ], + "github_reviewed_at": "2023-08-15T23:28:45Z", + "github_reviewed": true, + "nvd_published_at": "2014-08-26T14:55:00Z", + "severity": "HIGH" + }, + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2014-0481" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/26cd48e166ac4d84317c8ee6d63ac52a87e8da99" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/30042d475bf084c6723c6217a21598d9247a9c41" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/dd0c3f4ee1a30c1a1e6055061c6ba6e58c6b54d1" + }, + { + "type": "PACKAGE", + "url": "https://github.com/django/django" + }, + { + "type": "WEB", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2014-5.yaml" + }, + { + "type": "WEB", + "url": "https://www.djangoproject.com/weblog/2014/aug/20/security" + }, + { + "type": "WEB", + "url": "http://lists.opensuse.org/opensuse-updates/2014-09/msg00023.html" + }, + { + "type": "WEB", + "url": "http://www.debian.org/security/2014/dsa-3010" + } + ], + "affected": [ + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "1.4.14" + } + ] + } + ], + "versions": [ + "1.0.1", + "1.0.2", + "1.0.3", + "1.0.4", + "1.1", + "1.1.1", + "1.1.2", + "1.1.3", + "1.1.4", + "1.2", + "1.2.1", + "1.2.2", + "1.2.3", + "1.2.4", + "1.2.5", + "1.2.6", + "1.2.7", + "1.3", + "1.3.1", + "1.3.2", + "1.3.3", + "1.3.4", + "1.3.5", + "1.3.6", + "1.3.7", + "1.4", + "1.4.1", + "1.4.10", + "1.4.11", + "1.4.12", + "1.4.13", + "1.4.2", + "1.4.3", + "1.4.4", + "1.4.5", + "1.4.6", + "1.4.7", + "1.4.8", + "1.4.9" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/05/GHSA-296w-6qhq-gf92/GHSA-296w-6qhq-gf92.json" + } + }, + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "1.5" + }, + { + "fixed": "1.5.9" + } + ] + } + ], + "versions": [ + "1.5", + "1.5.1", + "1.5.2", + "1.5.3", + "1.5.4", + "1.5.5", + "1.5.6", + "1.5.7", + "1.5.8" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/05/GHSA-296w-6qhq-gf92/GHSA-296w-6qhq-gf92.json" + } + }, + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "1.6" + }, + { + "fixed": "1.6.6" + } + ] + } + ], + "versions": [ + "1.6", + "1.6.1", + "1.6.2", + "1.6.3", + "1.6.4", + "1.6.5" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/05/GHSA-296w-6qhq-gf92/GHSA-296w-6qhq-gf92.json" + } + } + ], + "schema_version": "1.7.3", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N" + } + ] + }, + { + "id": "GHSA-2gwj-7jmv-h26r", + "summary": "SQL Injection in Django", + "details": "An issue was discovered in Django 2.2 before 2.2.28, 3.2 before 3.2.13, and 4.0 before 4.0.4. `QuerySet.annotate()`, `aggregate()`, and `extra()` methods are subject to SQL injection in column aliases via a crafted dictionary (with dictionary expansion) as the passed `**kwargs`.", + "aliases": [ + "BIT-django-2022-28346", + "CVE-2022-28346", + "PYSEC-2022-190" + ], + "modified": "2025-02-21T05:41:10.759178Z", + "published": "2022-04-13T00:00:33Z", + "database_specific": { + "cwe_ids": [ + "CWE-89" + ], + "github_reviewed_at": "2022-04-22T20:33:03Z", + "github_reviewed": true, + "nvd_published_at": "2022-04-12T05:15:00Z", + "severity": "CRITICAL" + }, + "references": [ + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-28346" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/2044dac5c6968441be6f534c4139bcf48c5c7e48" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/2c09e68ec911919360d5f8502cefc312f9e03c5d" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/800828887a0509ad1162d6d407e94d8de7eafc60" + }, + { + "type": "WEB", + "url": "https://github.com/django/django/commit/93cae5cb2f9a4ef1514cf1a41f714fef08005200" + }, + { + "type": "WEB", + "url": "https://docs.djangoproject.com/en/4.0/releases/security" + }, + { + "type": "ADVISORY", + "url": "https://github.com/advisories/GHSA-2gwj-7jmv-h26r" + }, + { + "type": "PACKAGE", + "url": "https://github.com/django/django" + }, + { + "type": "WEB", + "url": "https://github.com/pypa/advisory-database/tree/main/vulns/django/PYSEC-2022-190.yaml" + }, + { + "type": "WEB", + "url": "https://groups.google.com/forum/#!forum/django-announce" + }, + { + "type": "WEB", + "url": "https://lists.debian.org/debian-lts-announce/2022/04/msg00013.html" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/HWY6DQWRVBALV73BPUVBXC3QIYUM24IK" + }, + { + "type": "WEB", + "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/LTZVAKU5ALQWOKFTPISE257VCVIYGFQI" + }, + { + "type": "WEB", + "url": "https://security.netapp.com/advisory/ntap-20220609-0002" + }, + { + "type": "WEB", + "url": "https://www.debian.org/security/2022/dsa-5254" + }, + { + "type": "WEB", + "url": "https://www.djangoproject.com/weblog/2022/apr/11/security-releases" + }, + { + "type": "WEB", + "url": "http://www.openwall.com/lists/oss-security/2022/04/11/1" + } + ], + "affected": [ + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "2.2" + }, + { + "fixed": "2.2.28" + } + ] + } + ], + "versions": [ + "2.2", + "2.2.1", + "2.2.10", + "2.2.11", + "2.2.12", + "2.2.13", + "2.2.14", + "2.2.15", + "2.2.16", + "2.2.17", + "2.2.18", + "2.2.19", + "2.2.2", + "2.2.20", + "2.2.21", + "2.2.22", + "2.2.23", + "2.2.24", + "2.2.25", + "2.2.26", + "2.2.27", + "2.2.3", + "2.2.4", + "2.2.5", + "2.2.6", + "2.2.7", + "2.2.8", + "2.2.9" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/04/GHSA-2gwj-7jmv-h26r/GHSA-2gwj-7jmv-h26r.json" + } + }, + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "3.2" + }, + { + "fixed": "3.2.13" + } + ] + } + ], + "versions": [ + "3.2", + "3.2.1", + "3.2.10", + "3.2.11", + "3.2.12", + "3.2.2", + "3.2.3", + "3.2.4", + "3.2.5", + "3.2.6", + "3.2.7", + "3.2.8", + "3.2.9" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/04/GHSA-2gwj-7jmv-h26r/GHSA-2gwj-7jmv-h26r.json" + } + }, + { + "package": { + "name": "django", + "ecosystem": "PyPI", + "purl": "pkg:pypi/django" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "4.0" + }, + { + "fixed": "4.0.4" + } + ] + } + ], + "versions": [ + "4.0", + "4.0.1", + "4.0.2", + "4.0.3" + ], + "database_specific": { + "source": "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2022/04/GHSA-2gwj-7jmv-h26r/GHSA-2gwj-7jmv-h26r.json" + } + } + ], + "schema_version": "1.7.3", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "type": "CVSS_V4", + "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N" + } + ] + } + ] +} \ No newline at end of file