From ce83ba927ddc27954ba3ae444f0bc452d6d86d29 Mon Sep 17 00:00:00 2001 From: JasonBuildAI Date: Sun, 11 Oct 2026 18:01:15 +0800 Subject: [PATCH] fix(capability): detect native and escaped Windows private paths in public-safe-outbound The builtin fail-closed pre-submission scrub only recognized the POSIX spelling of a private absolute path. On a Windows host the same private path written as C:\Users\\... or \\server\Users\\..., and the escaped form a serialized string produces (C:\\Users\\\\...), passed the scan with hit_count 0 while its POSIX spelling was blocked. #6161 repaired the same spelling class in the public/private boundary scan and names this rule as its deliberately deferred successor owner. Match a separator run at every junction of private_abs_path, covering the POSIX, native Windows, mixed, rooted and escaped spellings of the same roots, and pin the native, escaped and mixed forms with tests. Closes #6220 Follow-up to #6161 Signed-off-by: JasonBuildAI --- .../public_safe_outbound/scanner.py | 13 ++++- .../tests/test_scanner.py | 49 +++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/loopx/capabilities/public_safe_outbound/scanner.py b/loopx/capabilities/public_safe_outbound/scanner.py index 9828e01efa..32aca10ae8 100644 --- a/loopx/capabilities/public_safe_outbound/scanner.py +++ b/loopx/capabilities/public_safe_outbound/scanner.py @@ -27,8 +27,17 @@ {"id": "aklt_access_key", "label": "AK/SK access key", "pattern": r"\b" + "AKLT" + r"[A-Za-z0-9]{10,}\b"}, {"id": "private_key_block", "label": "private key block", "pattern": r"-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----"}, {"id": "api_key_assignment", "label": "api key assignment", "pattern": r"(?i)\b(api[_-]?key|access[_-]?key|secret)\b\s*[=:]\s*['\"][^'\"]{8,}['\"]"}, - # private absolute paths - {"id": "private_abs_path", "label": "private absolute path", "pattern": r"/(Users|home|data00|root)/([A-Za-z0-9_.-]+)/"}, + # Private absolute paths: the private roots under whatever separator the + # host or a serialized string chose. A separator run at every junction + # covers the POSIX spelling (/Users/alice/), the native Windows spellings + # (\Users\alice\ and C:\Users\alice\) and the mixed or escaped spellings of + # serialized text (\\Users\\alice\\), matching the boundary scan's + # separator-run class (#6161). + { + "id": "private_abs_path", + "label": "private absolute path", + "pattern": r"[\\/]+(?:Users|home|data00|root)[\\/]+[A-Za-z0-9_.-]+[\\/]+", + }, ) diff --git a/loopx/capabilities/public_safe_outbound/tests/test_scanner.py b/loopx/capabilities/public_safe_outbound/tests/test_scanner.py index e8dcefc4a6..7d668142d0 100644 --- a/loopx/capabilities/public_safe_outbound/tests/test_scanner.py +++ b/loopx/capabilities/public_safe_outbound/tests/test_scanner.py @@ -34,6 +34,55 @@ def test_detects_private_path() -> None: assert any(h.rule_id == "private_abs_path" for h in res.hits) +def test_detects_native_windows_private_path() -> None: + # The same private path in the spellings a Windows host produces: a drive + # letter and an UNC share. Assembled at runtime so the source stays clean of + # the literal pattern. + separator = "\\" + drive = separator.join(["C:", "Users", "example-user", "code-reading", "some_workspace"]) + unc = ( + separator + + separator.join(["", "fileserver", "Users", "example-user", "code-reading", "some_workspace"]) + ) + for label, private in (("drive", drive), ("unc", unc)): + res = scan_texts({label: "run from " + private}) + assert res.ok is False, label + assert any(h.rule_id == "private_abs_path" for h in res.hits), label + + +def test_detects_escaped_and_mixed_windows_private_path() -> None: + # Serialized text doubles each backslash, and a Windows host mixes + # separators when a POSIX-style segment meets a native one; both are the + # same private path and must be a hit, like the boundary scan sibling + # (#6161). Assembled at runtime so the source stays clean of the literal + # pattern. + slash = "/" + sep = "\\" + doubled = sep + sep + parts = ["Users", "example-user", "code-reading", "some_workspace"] + cases = { + "escaped-drive": "C:" + doubled + doubled.join(parts), + "escaped-unc": doubled + doubled + "fileserver" + doubled + "Users" + doubled + "example-user" + doubled, + "mixed-drive": "C:" + sep + slash.join(parts), + "mixed-unc": slash + slash + "fileserver" + sep + "Users" + sep + "example-user" + sep, + "rooted-native": sep + "Users" + sep + "example-user" + sep, + } + for label, private in cases.items(): + res = scan_texts({label: '{"cwd": "' + private + '"}'}) + assert res.ok is False, label + assert any(h.rule_id == "private_abs_path" for h in res.hits), label + + +def test_native_windows_path_outside_the_private_roots_stays_clean() -> None: + # The rule keeps the same root keywords as the POSIX spelling carried, so + # an ordinary drive path that is not under a private root must stay clean + # and cannot block a submission on its own. + separator = "\\" + public_path = separator.join(["C:", "Projects", "public-demo", "build"]) + res = scan_texts({"d": "build output lives under " + public_path}) + assert res.ok is True + + def test_clean_text_passes(tmp_path: Path) -> None: (tmp_path / "ok.md").write_text( "Public-safe note: compare baseline vs treatment on the same model/task/verifier.",