From 42264b219e1297ced8c1357c531bae7f8a307ca6 Mon Sep 17 00:00:00 2001 From: Dennis Lysenko Date: Mon, 5 Oct 2026 16:05:24 +0000 Subject: [PATCH] fix(report): dedupe doubled section headings in penetration_test_report.md The agent is prompted for per-section prose (executive_summary, methodology, technical_analysis, recommendations) and frequently prepends the very heading that _format_final_scan_result's template already emits, producing doubled H1s in the written report: # Executive Summary # Executive Summary An unauthenticated black-box security assessment... Observed on a real deep scan: every H1 (Executive Summary, Methodology, Technical Analysis, Recommendations) appeared twice in a row. Strip a leading heading line from each section's content when it duplicates the template heading. Case-insensitive; only the first line is examined, so a heading repeated later in the body (legitimate quoted content) is preserved. Adds tests covering the dedupe, the no-heading passthrough, and the later-repeat-preserved case. --- strix/report/state.py | 35 +++++++++++-------- tests/test_report_writer.py | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/strix/report/state.py b/strix/report/state.py index fd0ebd94..e1bdb648 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -806,22 +806,27 @@ class ReportState: self.save_run_data(status=status) def _format_final_scan_result(self, scan_results: dict[str, Any]) -> str: - return f"""# Executive Summary + def section(heading: str, content: object) -> str: + # The agent is asked for section prose and often prepends the very + # heading this template already emits (e.g. "# Executive Summary"), + # which doubled every H1 in penetration_test_report.md. Strip a + # leading heading line from the content when it duplicates the + # template heading. Case-insensitive; only the FIRST line is + # examined, so a repeated heading later in the body is untouched. + text = str(content or "").strip() + first_line, _, rest = text.partition("\n") + if first_line.strip().lstrip("#").strip().lower() == heading.lstrip("#").strip().lower() and first_line.lstrip().startswith("#"): + text = rest.strip() + return f"{heading}\n\n{text}" -{str(scan_results.get("executive_summary", "")).strip()} - -# Methodology - -{str(scan_results.get("methodology", "")).strip()} - -# Technical Analysis - -{str(scan_results.get("technical_analysis", "")).strip()} - -# Recommendations - -{str(scan_results.get("recommendations", "")).strip()} -""" + return "\n\n".join( + [ + section("# Executive Summary", scan_results.get("executive_summary", "")), + section("# Methodology", scan_results.get("methodology", "")), + section("# Technical Analysis", scan_results.get("technical_analysis", "")), + section("# Recommendations", scan_results.get("recommendations", "")), + ] + ) + "\n" def _coverage_document(self) -> dict[str, Any] | None: """Assemble the coverage record, or None when it can't be built. diff --git a/tests/test_report_writer.py b/tests/test_report_writer.py index 9b849010..f0549d91 100644 --- a/tests/test_report_writer.py +++ b/tests/test_report_writer.py @@ -265,3 +265,71 @@ def test_render_vulnerability_md_surfaces_calibration_metadata() -> None: assert "## Confidence Rationale" in md assert "## What Would Change This Severity" in md assert "## Fix Verification" in md + + +def test_format_final_scan_result_strips_duplicated_section_headings() -> None: + """The agent is asked for section prose and often prepends the same heading + the template already emits, doubling every H1 (# Executive Summary followed + by # Executive Summary). The formatter must strip the duplicated leading + heading while leaving the body — and any legitimately repeated heading later + in the body — intact.""" + from strix.report.state import ReportState + + state = ReportState(run_name="t") + result = state._format_final_scan_result( + { + "executive_summary": "# Executive Summary\n\nThe app is broadly sound.", + "methodology": "# Methodology\n\nBlack-box, GET/HEAD only.", + "technical_analysis": "# Technical Analysis\n\n## Target Profile\n\nDetails.", + "recommendations": "# Recommendations\n\n## Immediate\n\nFix CSP.", + } + ) + # Each template heading appears exactly once. + for heading in ( + "# Executive Summary", + "# Methodology", + "# Technical Analysis", + "# Recommendations", + ): + assert result.count(heading) == 1, f"{heading} doubled:\n{result}" + # Bodies and genuine sub-headings survive. + assert "The app is broadly sound." in result + assert "## Target Profile" in result + assert "## Immediate" in result + + +def test_format_final_scan_result_keeps_content_without_headings() -> None: + """Content that does not repeat the heading is left alone.""" + from strix.report.state import ReportState + + state = ReportState(run_name="t") + result = state._format_final_scan_result( + { + "executive_summary": "The app is broadly sound.", + "methodology": "Black-box.", + "technical_analysis": "Some analysis.", + "recommendations": "Fix things.", + } + ) + assert result.count("# Executive Summary") == 1 + assert "The app is broadly sound." in result + + +def test_format_final_scan_result_keeps_later_repeated_heading() -> None: + """Only the FIRST line is examined: a heading repeated later in the body is + legitimate content (e.g. a quoted section) and must be preserved.""" + from strix.report.state import ReportState + + state = ReportState(run_name="t") + body = "Intro.\n\n# Executive Summary\n\nA quoted recap below.\n" + result = state._format_final_scan_result( + { + "executive_summary": body, + "methodology": "m", + "technical_analysis": "t", + "recommendations": "r", + } + ) + # Template heading + the in-body repeat = two occurrences, not three. + assert result.count("# Executive Summary") == 2 + assert "A quoted recap below." in result