diff --git a/scanpipe/pipes/vulnerablecode.py b/scanpipe/pipes/vulnerablecode.py index 6c6073b5d0..297f288d73 100644 --- a/scanpipe/pipes/vulnerablecode.py +++ b/scanpipe/pipes/vulnerablecode.py @@ -223,6 +223,12 @@ def fetch_vulnerabilities( for purls_batch in chunked(get_purls(packages), chunk_size): response_data = bulk_search_by_purl(purls_batch) + if not response_data: + logger( + f"VulnerableCode did not return data for a batch of " + f"{len(purls_batch)} PURLs. Skipping batch." + ) + continue for vulnerability_data in response_data: vulnerabilities_by_purl[vulnerability_data["purl"]] = vulnerability_data diff --git a/scanpipe/tests/pipes/test_vulnerablecode.py b/scanpipe/tests/pipes/test_vulnerablecode.py index e39dbf69ed..34bc752569 100644 --- a/scanpipe/tests/pipes/test_vulnerablecode.py +++ b/scanpipe/tests/pipes/test_vulnerablecode.py @@ -64,6 +64,24 @@ def test_scanpipe_pipes_vulnerablecode_fetch_vulnerabilities( django_5_0.refresh_from_db() self.assertEqual(1, len(django_5_0.affected_by_vulnerabilities)) + @mock.patch("scanpipe.pipes.vulnerablecode.bulk_search_by_purl") + def test_fetch_vulnerabilities_handles_none_response(self, mock_search_by_purl): + """ + fetch_vulnerabilities must not crash when bulk_search_by_purl returns None. + + This happens when VulnerableCode is unreachable or returns an error, + causing request_post() to return None implicitly. + """ + package = make_package(self.project1, "pkg:pypi/requests@2.31.0") + mock_search_by_purl.return_value = None + + buffer = io.StringIO() + fetch_vulnerabilities(packages=[package], logger=buffer.write) + + package.refresh_from_db() + self.assertEqual([], package.affected_by_vulnerabilities) + self.assertIn("VulnerableCode did not return data", buffer.getvalue()) + def test_scanpipe_pipes_vulnerablecode_filter_vulnerabilities(self): data = self.data / "vulnerablecode/django-5.0_package_data.json" package_data = json.loads(data.read_text())