mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-19 04:59:28 +00:00
search-with-proof: family-to-oracle mapping, build memory, tolerant tool calls
Demos declare their oracle within the family allowed set (injection: sql or command; output_encoding: xss; untrusted_destination: ssrf; path: path); builds and app starts use the search memory budget instead of the 256 MB tool limit (npm died with SIGTRAP); malformed tool-call JSON is fed back once. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
c190d34f60
commit
bf243e6680
@@ -27,7 +27,8 @@ from typing import Any, cast
|
||||
|
||||
from ..sandbox import SandboxJob, SandboxResult
|
||||
from . import _sandbox, task_storage
|
||||
from .demo import BUILD_RECIPES, RUNTIMES, capture_output, load_demo, substitute
|
||||
from .budget import SearchBudget
|
||||
from .demo import BUILD_RECIPES, FAMILY_ORACLES, RUNTIMES, capture_output, load_demo, substitute, validate_oracle
|
||||
from .oracles import BrowserExecutor, Canary, Oracle, oracle_for
|
||||
from .task_storage import exception_reason
|
||||
|
||||
@@ -277,7 +278,7 @@ def _side(side: Side, demo: Path, family: str, timeout: int, browser: BrowserExe
|
||||
mounts = {f"/deps/{i}": p for i, p in enumerate(dependencies)}
|
||||
|
||||
def job(command: tuple[str, ...], checkout: Path = checkout) -> SandboxJob:
|
||||
return SandboxJob("", command, checkout, {}, timeout, 256, 128)
|
||||
return SandboxJob("", command, checkout, {}, timeout, SearchBudget().memory_bytes // 1024**2, 128)
|
||||
|
||||
products = root / "products"
|
||||
if side.products is not None:
|
||||
@@ -448,7 +449,7 @@ def _run_task(
|
||||
|
||||
def limits() -> None:
|
||||
resource.setrlimit(resource.RLIMIT_FSIZE, (1024 * 1024,) * 2)
|
||||
resource.setrlimit(resource.RLIMIT_AS, (job.memory_mb * 1024**2,) * 2)
|
||||
# The external task memory limit bounds app starts; RLIMIT_AS breaks V8 reservations.
|
||||
resource.setrlimit(resource.RLIMIT_CPU, (job.timeout_seconds + 1,) * 2)
|
||||
|
||||
# Driver JSON includes command paths, but arbitrary user input stays data.
|
||||
@@ -481,13 +482,15 @@ def verify_side_task(side: Side, demo: Path, family: str, timeout_seconds: int =
|
||||
|
||||
This explicit API is never selected as a fallback in the host verifier.
|
||||
"""
|
||||
load_demo(demo)
|
||||
schema = load_demo(demo)
|
||||
if family in FAMILY_ORACLES and not FAMILY_ORACLES[family]:
|
||||
return SideRecord("no_oracle", (), 0, "no owned oracle", isolation_mode="task-boundary")
|
||||
validate_oracle(family, schema["oracle"])
|
||||
kind = schema["oracle"]
|
||||
if timeout_seconds < 1:
|
||||
raise ValueError("timeout must be positive")
|
||||
browser = BrowserExecutor(task_boundary=True) if family == "xss" else None
|
||||
if oracle_for(family, browser) is None:
|
||||
return SideRecord("no_oracle", (), 0, "no owned oracle", isolation_mode="task-boundary")
|
||||
return _side(side, demo, family, timeout_seconds, browser, task_boundary=True)
|
||||
browser = BrowserExecutor(task_boundary=True) if kind == "xss" else None
|
||||
return _side(side, demo, kind, timeout_seconds, browser, task_boundary=True)
|
||||
|
||||
|
||||
def verify(
|
||||
@@ -497,17 +500,24 @@ def verify(
|
||||
family: str,
|
||||
*,
|
||||
timeout_seconds: int = 5,
|
||||
oracle: str | None = None,
|
||||
browser: BrowserExecutor | None = None,
|
||||
task_dispatcher: Callable[..., SideRecord] | None = None,
|
||||
) -> VerificationRecord:
|
||||
"""Affected is vulnerable/head; safe is fixed/base. No negative verdict exists."""
|
||||
started = time.monotonic()
|
||||
if oracle_for(family, browser) is None:
|
||||
if family in FAMILY_ORACLES and not FAMILY_ORACLES[family]:
|
||||
return VerificationRecord("no_oracle", (), time.monotonic() - started, "no configured owned oracle for " + family)
|
||||
if timeout_seconds < 1:
|
||||
raise ValueError("timeout must be positive")
|
||||
try:
|
||||
load_demo(artefact)
|
||||
schema = load_demo(artefact)
|
||||
validate_oracle(family, schema["oracle"])
|
||||
if oracle is not None and oracle != schema["oracle"]:
|
||||
raise ValueError("oracle differs from demo")
|
||||
kind = schema["oracle"]
|
||||
if oracle_for(kind, browser) is None:
|
||||
raise ValueError("oracle runtime unavailable")
|
||||
except (OSError, ValueError) as exc:
|
||||
return VerificationRecord("inconclusive", (), time.monotonic() - started, "invalid declarative demo: " + exception_reason(exc))
|
||||
mode = "task-boundary" if task_dispatcher is not None else _sandbox.isolation_mode()
|
||||
@@ -527,7 +537,7 @@ def verify(
|
||||
if any(s.isolation_mode != "task-boundary" for s in sides):
|
||||
raise _sandbox.IsolationUnavailable("dispatcher did not attest task-boundary isolation")
|
||||
else:
|
||||
sides = tuple(_side(s, artefact, family, timeout_seconds, browser) for s in (affected, safe))
|
||||
sides = tuple(_side(s, artefact, kind, timeout_seconds, browser) for s in (affected, safe))
|
||||
if any(s.outcome == "observed" and len(s.runs) != 3 for s in sides):
|
||||
outcome, reason = "inconclusive", "each side requires exactly three fresh observations"
|
||||
elif sides[1].outcome != "observed":
|
||||
|
||||
Reference in New Issue
Block a user