From a5826baa9700611503015e08a72398ba72c032de Mon Sep 17 00:00:00 2001 From: RMANOV <96174405+RMANOV@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:15:32 +0300 Subject: [PATCH] fix(testing): redact windows scenario paths --- python/tests/test_strix_scenario_contract.py | 12 ++++++++++++ scripts/strix_scenario_contract.py | 20 ++++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/python/tests/test_strix_scenario_contract.py b/python/tests/test_strix_scenario_contract.py index 67845bf..78a8b51 100644 --- a/python/tests/test_strix_scenario_contract.py +++ b/python/tests/test_strix_scenario_contract.py @@ -102,6 +102,18 @@ def test_validate_scenario_read_errors_are_public_safe(tmp_path): assert str(tmp_path) not in result["errors"][0] +def test_public_exception_message_redacts_windows_paths(): + module = _load_module() + windows_path = "C:\\tmp\\missing.yaml" + error = FileNotFoundError(2, "No such file or directory", windows_path) + + sanitized = module.public_exception_message(error, Path(windows_path)) + + assert windows_path not in sanitized + assert repr(windows_path)[1:-1] not in sanitized + assert "/missing.yaml" in sanitized + + def test_public_scenarios_satisfy_contract(): module = _load_module() scenario_dir = Path(__file__).resolve().parents[2] / "sim" / "scenarios" diff --git a/scripts/strix_scenario_contract.py b/scripts/strix_scenario_contract.py index 9a3ee3c..bbe7cad 100644 --- a/scripts/strix_scenario_contract.py +++ b/scripts/strix_scenario_contract.py @@ -9,7 +9,7 @@ import json import re import sys -from pathlib import Path +from pathlib import Path, PureWindowsPath from typing import Any try: @@ -29,12 +29,24 @@ def public_path(path: Path) -> str: """Return a report-safe path without leaking local checkout layout.""" + path_str = str(path) + if re.match(r"^[A-Za-z]:[\\/]", path_str) or path_str.startswith("\\\\"): + name = PureWindowsPath(path_str).name or "." + return f"/{name}" if path.is_relative_to(ROOT): return str(path.relative_to(ROOT)) if path.is_absolute(): name = path.name or "." return f"/{name}" - return str(path) + return path_str + + +def redaction_variants(value: str) -> set[str]: + variants = {value} + variants.add(repr(value)[1:-1]) + variants.add(value.replace("\\", "\\\\")) + variants.add(value.replace("\\\\", "\\")) + return {variant for variant in variants if variant} def public_exception_message(exc: Exception, path: Path) -> str: @@ -50,8 +62,8 @@ def public_exception_message(exc: Exception, path: Path) -> str: replacements[str(candidate_path)] = public_path(candidate_path) for raw, safe in replacements.items(): - if raw: - message = message.replace(raw, safe) + for variant in redaction_variants(raw): + message = message.replace(variant, safe) return message