From 6c7d6bb1d9e3704839c96e8d56ce4b64dff42a8a Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:24:31 +0200 Subject: [PATCH] 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 --- src/openultrasast/search/oracles.py | 4 ++++ src/openultrasast/search/worker.py | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/openultrasast/search/oracles.py b/src/openultrasast/search/oracles.py index 61c7e07..72aca91 100644 --- a/src/openultrasast/search/oracles.py +++ b/src/openultrasast/search/oracles.py @@ -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 diff --git a/src/openultrasast/search/worker.py b/src/openultrasast/search/worker.py index 3b32c45..682742a 100644 --- a/src/openultrasast/search/worker.py +++ b/src/openultrasast/search/worker.py @@ -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}. "