Skip to content

Commit efee7d5

Browse files
committed
Fix TypeError crash in fetch_vulnerabilities when VulnerableCode is unreachable
bulk_search_by_purl() returns None when request_post() catches a network error or HTTP failure. The loop in fetch_vulnerabilities() iterated over the result directly, crashing with TypeError: 'NoneType' is not iterable. Add a guard that skips the batch and logs a warning when response_data is falsy, so a VulnerableCode outage degrades gracefully instead of aborting the entire pipeline run. Fixes #2107 Signed-off-by: Harsh Verma <harshkardam246@gmail.com>
1 parent 2090ea0 commit efee7d5

2 files changed

Lines changed: 23 additions & 0 deletions

File tree

scanpipe/pipes/vulnerablecode.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,12 @@ def fetch_vulnerabilities(
223223

224224
for purls_batch in chunked(get_purls(packages), chunk_size):
225225
response_data = bulk_search_by_purl(purls_batch)
226+
if not response_data:
227+
logger(
228+
f"VulnerableCode did not return data for a batch of "
229+
f"{len(purls_batch)} PURLs. Skipping batch."
230+
)
231+
continue
226232
for vulnerability_data in response_data:
227233
vulnerabilities_by_purl[vulnerability_data["purl"]] = vulnerability_data
228234

scanpipe/tests/pipes/test_vulnerablecode.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,23 @@ def test_scanpipe_pipes_vulnerablecode_fetch_vulnerabilities(
6464
django_5_0.refresh_from_db()
6565
self.assertEqual(1, len(django_5_0.affected_by_vulnerabilities))
6666

67+
@mock.patch("scanpipe.pipes.vulnerablecode.bulk_search_by_purl")
68+
def test_fetch_vulnerabilities_handles_none_response(self, mock_search_by_purl):
69+
"""fetch_vulnerabilities must not crash when bulk_search_by_purl returns None.
70+
71+
This happens when VulnerableCode is unreachable or returns an error,
72+
causing request_post() to return None implicitly.
73+
"""
74+
package = make_package(self.project1, "pkg:pypi/requests@2.31.0")
75+
mock_search_by_purl.return_value = None
76+
77+
buffer = io.StringIO()
78+
fetch_vulnerabilities(packages=[package], logger=buffer.write)
79+
80+
package.refresh_from_db()
81+
self.assertEqual([], package.affected_by_vulnerabilities)
82+
self.assertIn("VulnerableCode did not return data", buffer.getvalue())
83+
6784
def test_scanpipe_pipes_vulnerablecode_filter_vulnerabilities(self):
6885
data = self.data / "vulnerablecode/django-5.0_package_data.json"
6986
package_data = json.loads(data.read_text())

0 commit comments

Comments
 (0)