From 788baeadbb2f39e8ecd89ba195dbc296e406ffe5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 30 Sep 2026 02:41:26 +0900 Subject: [PATCH] fix(strix): accept PR-scope directories that contain a changed file fast-mlsirm#2246's Strix rerun (run 36580588738, attempt 2) completed with zero findings and a substantive report whose scope section listed /workspace/strix-pr-scope.AoFHD6/crates/mlsirm-core, .../crates/fast-mlsirm-py and .../python/fast_mlsirm. #2474's validator only accepts a verbatim changed file path, so the gate failed closed with "scan report does not identify a changed source file". Also accept a path under this scan's private PR-scope root (which holds only the changed files) when it is a changed file or a directory containing one. The scope root itself, a repository-root equivalent, bare repository directories without the scope prefix, and directories that contain no changed file still fail, so generic reports stay rejected as #2474 intended. Tests: the 0-finding #2246 scope section on Rust/Python changed files passes (red before the fix); scope-root-only, '.', unrelated directory and bare directory all reject. The real artifact fails on main and passes here. Full test_strix_quick_gate.sh PASS; 208 strix tests pass in both modes. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PtgtJk1WjqieqvLDb1w8uW --- ...20260930-strix-report-scope-directories.md | 8 +++ scripts/ci/strix_report_scope.py | 16 +++++- tests/test_strix_report_scope.py | 51 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.d/20260930-strix-report-scope-directories.md diff --git a/CHANGELOG.d/20260930-strix-report-scope-directories.md b/CHANGELOG.d/20260930-strix-report-scope-directories.md new file mode 100644 index 0000000000..708a8127d6 --- /dev/null +++ b/CHANGELOG.d/20260930-strix-report-scope-directories.md @@ -0,0 +1,8 @@ +## Fixed + +- A completed Strix PR scan whose report names the scanned PR-scope + directory that contains a changed file (for example + `/workspace/strix-pr-scope./crates/core`) now counts as scoped. Such + reports were rejected with "scan report does not identify a changed source + file" even when they described the changed code. The scope root itself and + unrelated directories still do not count. diff --git a/scripts/ci/strix_report_scope.py b/scripts/ci/strix_report_scope.py index 44de1c441d..cc374c5852 100644 --- a/scripts/ci/strix_report_scope.py +++ b/scripts/ci/strix_report_scope.py @@ -4,9 +4,23 @@ from __future__ import annotations import json +import re import sys from pathlib import Path +# The PR scan target is a private directory holding only the changed files, so +# a path under it (as Strix reports it) is bound to this invocation's scope. +SCOPE_PATH_RE = re.compile(r"/workspace/strix-pr-scope\.[A-Za-z0-9]+/([A-Za-z0-9_./-]+)") + + +def _names_scoped_ancestor(report: str, changed_paths: list[str]) -> bool: + """True when the report names a PR-scope directory or file containing a changed path.""" + for match in SCOPE_PATH_RE.finditer(report): + named = match.group(1).strip("/") + if named and any(path == named or path.startswith(named + "/") for path in changed_paths): + return True + return False + def validate(output: Path, changed_paths: list[str]) -> None: if not output.is_dir() or output.is_symlink(): @@ -28,7 +42,7 @@ def validate(output: Path, changed_paths: list[str]) -> None: if metadata.get("status") != "completed" or results.get("scan_completed") is not True or results.get("success") is not True: raise ValueError("scan report is incomplete") report = report_path.read_text(encoding="utf-8") - if not any(path in report for path in changed_paths): + if not any(path in report for path in changed_paths) and not _names_scoped_ancestor(report, changed_paths): raise ValueError("scan report does not identify a changed source file") diff --git a/tests/test_strix_report_scope.py b/tests/test_strix_report_scope.py index 3ff7964659..284b34e8d5 100644 --- a/tests/test_strix_report_scope.py +++ b/tests/test_strix_report_scope.py @@ -5,7 +5,6 @@ import sys from pathlib import Path - SCRIPT = Path(__file__).resolve().parents[1] / "scripts/ci/strix_report_scope.py" @@ -24,3 +23,53 @@ def test_report_scope_rejects_unrelated_success_and_accepts_scoped_success(tmp_p report.write_text("Assessed python/fast_mlsirm/report.py; no vulnerabilities found.\n", encoding="utf-8") assert subprocess.run(command, capture_output=True).returncode == 0 assert subprocess.run(command[:-1], capture_output=True).returncode == 1 + + +# Scope section of the 0-finding fast-mlsirm#2246 report (run 36580588738, +# attempt 2): it names the PR-scope directories it audited, not a file. +SCOPED_DIRECTORY_REPORT = """# Methodology + +**Scope:** +- `/workspace/strix-pr-scope.AoFHD6/crates/mlsirm-core` (Core Rust implementation) +- `/workspace/strix-pr-scope.AoFHD6/crates/fast-mlsirm-py` (PyO3 bindings) +- `/workspace/strix-pr-scope.AoFHD6/python/fast_mlsirm` (Python wrapper) + +No security vulnerabilities were identified during this assessment. +""" + + +def _completed_run(tmp_path: Path, report: str) -> None: + run = tmp_path / "current-scan" + run.mkdir() + (run / "run.json").write_text( + json.dumps({"status": "completed", "scan_results": {"scan_completed": True, "success": True}}), + encoding="utf-8", + ) + (run / "penetration_test_report.md").write_text(report, encoding="utf-8") + + +def test_scan_scope_directory_containing_a_changed_file_identifies_the_scope(tmp_path: Path) -> None: + _completed_run(tmp_path, SCOPED_DIRECTORY_REPORT) + changed = ["crates/mlsirm-core/src/gpu_regression.rs", "python/fast_mlsirm/regression.py"] + assert subprocess.run([sys.executable, str(SCRIPT), str(tmp_path), *changed], capture_output=True).returncode == 0 + + +def test_scan_scope_directory_unrelated_to_changed_files_is_rejected(tmp_path: Path) -> None: + _completed_run(tmp_path, SCOPED_DIRECTORY_REPORT) + changed = ["docs/methods.md", "tests/test_regression.py"] + assert subprocess.run([sys.executable, str(SCRIPT), str(tmp_path), *changed], capture_output=True).returncode == 1 + + +def test_bare_repository_directory_or_scope_root_is_not_enough(tmp_path: Path) -> None: + changed = ["crates/mlsirm-core/src/gpu_regression.rs"] + _completed_run(tmp_path, "Audited crates/mlsirm-core; nothing found.\n") + assert subprocess.run([sys.executable, str(SCRIPT), str(tmp_path), *changed], capture_output=True).returncode == 1 + (tmp_path / "current-scan" / "penetration_test_report.md").write_text( + "Scope: `/workspace/strix-pr-scope.AoFHD6/`; nothing found.\n", encoding="utf-8" + ) + assert subprocess.run([sys.executable, str(SCRIPT), str(tmp_path), *changed], capture_output=True).returncode == 1 + # A repository-root equivalent under the scope root binds nothing either. + (tmp_path / "current-scan" / "penetration_test_report.md").write_text( + "Scope: `/workspace/strix-pr-scope.AoFHD6/.`; nothing found.\n", encoding="utf-8" + ) + assert subprocess.run([sys.executable, str(SCRIPT), str(tmp_path), *changed], capture_output=True).returncode == 1