From bbcee7d81450a636b54b4cf97a241f39dd36b9bb Mon Sep 17 00:00:00 2001 From: Scott Severance Date: Fri, 4 Sep 2026 07:47:29 +0000 Subject: [PATCH 1/2] fix: add validation for empty zeroday LLM responses --- vulntester/llm/analyzer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vulntester/llm/analyzer.py b/vulntester/llm/analyzer.py index 0566403..d602c34 100644 --- a/vulntester/llm/analyzer.py +++ b/vulntester/llm/analyzer.py @@ -37,7 +37,7 @@ def _raw_analysis_text(response: str) -> str: def _finding_index(item: dict) -> int: """Return the 1-based ``finding_index``, or 0 when it is missing or not a number. - Models sometimes emit the index as a string (``"finding_index": "1"``). + Models sometimes emit the index as a string (``"finding_index": "1"``) Subtracting from that raises ``TypeError``, which would abort the whole run, so coerce here and let one bad element degrade to "untitled" instead. """ @@ -393,6 +393,7 @@ def _analyze_zeroday(self, anomalies: list[ZeroDayAnomaly]) -> list[dict]: if not response or not response.strip(): logger.error("LLM returned empty response for zero-day analysis") return [{"finding_title": "Zero-Day Analysis", "error": "LLM returned empty response"}] + return [{"finding_title": "Zero-Day Analysis", "raw_analysis": response}] def _generate_action_plan(self, report: ScanReport) -> str: From 842bf6c17eceaa8307e5ebf6d095422f062ea525 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 07:51:12 +0000 Subject: [PATCH 2/2] fix(review): revert docstring regression and no-op diff, add missing zeroday empty-response test The zero-day empty-response guard this PR claimed to add already exists verbatim on main; the only functional change was an unrelated docstring edit that dropped a sentence-ending period. Revert analyzer.py to match main, and add the regression test for that guard that was missing from the suite (verified it fails without the existing guard). --- tests/test_llm_analyzer.py | 12 ++++++++++++ vulntester/llm/analyzer.py | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/test_llm_analyzer.py b/tests/test_llm_analyzer.py index c713d7d..9dba071 100644 --- a/tests/test_llm_analyzer.py +++ b/tests/test_llm_analyzer.py @@ -133,6 +133,18 @@ def test_empty_narrative_section_is_flagged(analyzer): assert "Analysis unavailable" in results["executive_summary"] +def test_empty_zeroday_response_records_an_error(analyzer): + anomaly = ZeroDayAnomaly(detector="d", title="t", score=90, description="desc") + with patch( + "urllib.request.urlopen", side_effect=lambda *a, **k: _FakeResponse(_claude_text("")) + ): + results = analyzer._analyze_zeroday([anomaly]) + + assert len(results) == 1 + assert "raw_analysis" not in results[0] + assert "empty response" in results[0]["error"] + + def test_analyze_single_finding_propagates_failure(analyzer): with patch("urllib.request.urlopen", side_effect=_http_error(401)): with pytest.raises(LLMError): diff --git a/vulntester/llm/analyzer.py b/vulntester/llm/analyzer.py index d602c34..0566403 100644 --- a/vulntester/llm/analyzer.py +++ b/vulntester/llm/analyzer.py @@ -37,7 +37,7 @@ def _raw_analysis_text(response: str) -> str: def _finding_index(item: dict) -> int: """Return the 1-based ``finding_index``, or 0 when it is missing or not a number. - Models sometimes emit the index as a string (``"finding_index": "1"``) + Models sometimes emit the index as a string (``"finding_index": "1"``). Subtracting from that raises ``TypeError``, which would abort the whole run, so coerce here and let one bad element degrade to "untitled" instead. """ @@ -393,7 +393,6 @@ def _analyze_zeroday(self, anomalies: list[ZeroDayAnomaly]) -> list[dict]: if not response or not response.strip(): logger.error("LLM returned empty response for zero-day analysis") return [{"finding_title": "Zero-Day Analysis", "error": "LLM returned empty response"}] - return [{"finding_title": "Zero-Day Analysis", "raw_analysis": response}] def _generate_action_plan(self, report: ScanReport) -> str: