Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions loopx/capabilities/public_safe_outbound/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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_.-]+[\\/]+",
},
)


Expand Down
49 changes: 49 additions & 0 deletions loopx/capabilities/public_safe_outbound/tests/test_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
Loading