search-with-proof: path oracle covers the prefix-check-bypass class (sibling canary)

The path oracle only modelled classic parent traversal (a `canary` file in the parent of
${served_root}). A common real path-traversal class is the prefix-check bypass:
startswith(base) without a trailing separator, which the fix closes with base + os.sep;
it reaches a SIBLING whose path string-extends the served directory, not the parent.
PathOracle now also plants a sibling canary `public_leak` beside the served `public`
(same nonce, 0600), so a prefix-bypass demo leaks it on the vulnerable side and the fix
blocks it. ORACLE_GUIDE names the second target and tells the agent to pick the vector
its code shows. One shared nonce stays correct: a canary leaks only when the demo's own
request reaches it, and each demo targets one vector whose fix blocks it on the fixed side.

observe()/capture()/the differential/SideRecord are UNCHANGED. Local glance ceiling proof
still demonstrates (parent canary, 3/3 vuln, 0/3 fixed, control on both sides). Gate:
worker+control+verify suites 106 passed / 4 socket-skips; ruff clean; mypy clean on oracles.py.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-07 11:24:31 +02:00
co-authored by Claude Opus 4.8
parent 4c356caa21
commit 6c7d6bb1d9
2 changed files with 8 additions and 3 deletions
+4
View File
@@ -92,6 +92,10 @@ class PathOracle(OutputOracle):
(canary.root / "public").mkdir()
(canary.root / "canary").write_text(canary.nonce)
(canary.root / "canary").chmod(0o600)
# Parent canary covers classic traversal; this sibling covers prefix checks
# using startswith without a separator.
(canary.root / "public_leak").write_text(canary.nonce)
(canary.root / "public_leak").chmod(0o600)
# A benign in-root control file, distinct from the out-of-root secret, so a
# correctly serving app returns it on BOTH revisions without any traversal.
# Drawn from os.urandom, not secrets.token_hex, so it never perturbs callers
+4 -3
View File
@@ -102,9 +102,10 @@ ORACLE_GUIDE = (
"Explore reachability/disclosure using app code/own sandbox data; demos target below. "
"Only differential verification across revisions proves it. "
"Path observes app output for private nonce file canary in ${served_root}'s parent. "
"MUST set the app's served/root to exactly the ${served_root} placeholder via its command-line argument "
"(e.g. --dir ${served_root}), environment variable or config in start.files. "
"It lives relative to ${served_root}; serving elsewhere makes it unreachable: the search cannot succeed. "
"Prefix-sibling nonce file public_leak beside public extends the served name: reachable with startswith WITHOUT "
"a trailing separator until the fix adds one. "
"Target the code's vector: classic parent traversal or prefix-bypass. "
"MUST serve ${served_root} via CLI, environment or start.files. "
"SQL observes secret rows in ${database_path} (${database_url} is its SQLite URL). "
"Command observes marker creation at marker location ${marker_dir}/marker. "
"SSRF observes the nonce URL at its loopback listener ${callback_url}. "