From 4b47d42c9de2631c027ea1eb47960dd46a9a418d Mon Sep 17 00:00:00 2001 From: Alex Schapiro Date: Mon, 31 Aug 2026 03:27:52 +0000 Subject: [PATCH] report: rewrite finding markdown when resume cleans its title --- strix/report/state.py | 6 +++++- tests/test_list_reports.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/strix/report/state.py b/strix/report/state.py index 594925ba..ed7fd0e1 100644 --- a/strix/report/state.py +++ b/strix/report/state.py @@ -237,10 +237,14 @@ class ReportState: self.vulnerability_reports = [r for r in data if isinstance(r, dict)] for r in self.vulnerability_reports: title = r.get("title") + stale_md = False if isinstance(title, str): r["title"] = _clean_title(title) + stale_md = r["title"] != title rid = r.get("id") - if isinstance(rid, str): + # A finding already on disk keeps its markdown, unless cleaning + # changed the title: the heading on disk then needs a rewrite. + if isinstance(rid, str) and not stale_md: self._saved_vuln_ids.add(rid) logger.info( "report state hydrated %d vulnerability report(s)", diff --git a/tests/test_list_reports.py b/tests/test_list_reports.py index 373439cd..450721a3 100644 --- a/tests/test_list_reports.py +++ b/tests/test_list_reports.py @@ -48,13 +48,29 @@ def test_hydrate_from_run_dir_strips_control_chars_from_title( # A run started before titles were normalized can hold control characters on # disk, and resume re-exports those titles to the CSV, the SARIF and the TUI. (report_state.get_run_dir() / "vulnerabilities.json").write_text( - json.dumps([{"id": "vuln-0001", "title": "XSS in\r\n search\tform", "severity": "medium"}]), + json.dumps( + [ + { + "id": "vuln-0001", + "title": "XSS in\r\n search\tform", + "severity": "medium", + "timestamp": "2026-01-01 00:00:00 UTC", + } + ] + ), encoding="utf-8", ) + md_path = report_state.get_run_dir() / "vulnerabilities" / "vuln-0001.md" + md_path.parent.mkdir(exist_ok=True) + md_path.write_text("# XSS in\r\n search\tform\n", encoding="utf-8") + report_state.hydrate_from_run_dir() + report_state.save_run_data() assert report_state.vulnerability_reports[0]["title"] == "XSS in search form" + # The markdown on disk holds the raw heading, so resume must rewrite it. + assert md_path.read_text(encoding="utf-8").startswith("# XSS in search form\n") def _seed(state: ReportState) -> None: