mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 20:49:28 +00:00
chore(p0): retire regress, the skill router, the module audit tool and vocabulary.sc
Off every active line (PATHFINDER-2026-10-13/03-unified-proposal.md, cuts). Deleted: src/openultrasast/regress/ and the deep-mode REGRESS branch (deep now records a regress_retired degradation and equals standard; proof is the search-with-proof arm's job), the sandbox docker runner and probe that only regress used, --fail-on worth-fixing, RegressConfig, the two ledger helpers only regress called; skills.py and index.py with the hunter pool's skill routing; model/audit.py; cpg/queries/vocabulary.sc with benchmarks/push/ontology_gap.py. Docs and the scan skill describe the retirement. Net: 3,895 lines deleted, 86 added. Targeted tests: 180 passed, 28 skipped; 3,331 tests collect. Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
94e96c6885
commit
f5a904b7f2
@@ -1,25 +1,5 @@
|
||||
"""Isolated Docker CLI sandbox."""
|
||||
"""Shared sandbox job and result types."""
|
||||
|
||||
from .probe import FakeSandboxProbe, SandboxProbe, resolve_sandbox_probe
|
||||
from .runner import (
|
||||
DockerCliRunner,
|
||||
FakeSandboxRunner,
|
||||
SandboxJob,
|
||||
SandboxResult,
|
||||
SandboxRunner,
|
||||
build_docker_argv,
|
||||
resolve_sandbox_runner,
|
||||
)
|
||||
from .runner import SandboxJob, SandboxResult
|
||||
|
||||
__all__ = [
|
||||
"DockerCliRunner",
|
||||
"FakeSandboxProbe",
|
||||
"FakeSandboxRunner",
|
||||
"SandboxJob",
|
||||
"SandboxProbe",
|
||||
"SandboxResult",
|
||||
"SandboxRunner",
|
||||
"build_docker_argv",
|
||||
"resolve_sandbox_probe",
|
||||
"resolve_sandbox_runner",
|
||||
]
|
||||
__all__ = ["SandboxJob", "SandboxResult"]
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
"""Docker CLI presence and usability probe."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from collections.abc import Callable
|
||||
from subprocess import CompletedProcess
|
||||
|
||||
PROBE_TIMEOUT_SECONDS = 5
|
||||
PROBE_ENV = "OPENULTRASAST_SANDBOX_PROBE"
|
||||
|
||||
_DOCKER_INFO = ("docker", "info")
|
||||
_PROBE_OFF = frozenset({"0", "off", "false", "unavailable", "no"})
|
||||
_PROBE_ON = frozenset({"1", "on", "true", "available", "yes"})
|
||||
|
||||
|
||||
class SandboxProbe:
|
||||
"""Report whether `docker info` succeeds without raising into the scan."""
|
||||
|
||||
def __init__(self, *, runner: Callable[..., CompletedProcess[str]] | None = None) -> None:
|
||||
self._runner = runner
|
||||
|
||||
def available(self) -> bool:
|
||||
run = self._runner if self._runner is not None else subprocess.run
|
||||
try:
|
||||
result = run(
|
||||
list(_DOCKER_INFO),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=PROBE_TIMEOUT_SECONDS,
|
||||
check=False,
|
||||
)
|
||||
return result.returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class FakeSandboxProbe:
|
||||
"""Test double that reports a programmed availability without touching Docker."""
|
||||
|
||||
def __init__(self, available: bool) -> None:
|
||||
self._available = available
|
||||
|
||||
def available(self) -> bool:
|
||||
return self._available
|
||||
|
||||
|
||||
def resolve_sandbox_probe() -> SandboxProbe | FakeSandboxProbe:
|
||||
"""Return a real probe, or a fake when OPENULTRASAST_SANDBOX_PROBE is set."""
|
||||
flag = os.environ.get(PROBE_ENV, "").strip().lower()
|
||||
if flag in _PROBE_OFF:
|
||||
return FakeSandboxProbe(False)
|
||||
if flag in _PROBE_ON:
|
||||
return FakeSandboxProbe(True)
|
||||
return SandboxProbe()
|
||||
@@ -1,28 +1,9 @@
|
||||
"""Docker CLI sandbox runner that never mounts the host Docker socket."""
|
||||
"""Job and result types shared by the search sandbox and verifier."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from subprocess import CompletedProcess
|
||||
from typing import Protocol
|
||||
|
||||
NONROOT_USER = "65534:65534"
|
||||
WORKSPACE_MOUNT = "/workspace"
|
||||
SCRATCH_MOUNT = "/scratch"
|
||||
SCRATCH_SEED_MOUNT = "/scratch-in"
|
||||
RUNNER_ENV = "OPENULTRASAST_SANDBOX_RUNNER"
|
||||
_COPY_SEED_THEN_EXEC = 'cp -r /scratch-in/. /scratch/ && exec "$@"'
|
||||
_SEED_ARGV0 = "ousast-sandbox"
|
||||
_FAKE_RUNNER = frozenset({"fake"})
|
||||
|
||||
|
||||
class SandboxRunner(Protocol):
|
||||
def run(self, spec: SandboxJob) -> SandboxResult: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -43,158 +24,3 @@ class SandboxResult:
|
||||
stderr: str
|
||||
timed_out: bool
|
||||
|
||||
|
||||
class FakeSandboxRunner:
|
||||
"""Record jobs and return a programmed result without a Docker daemon."""
|
||||
|
||||
def __init__(self, result: SandboxResult | None = None) -> None:
|
||||
self.jobs: list[SandboxJob] = []
|
||||
self.result = result if result is not None else SandboxResult(exit_code=0, stdout="", stderr="", timed_out=False)
|
||||
|
||||
def run(self, spec: SandboxJob) -> SandboxResult:
|
||||
self.jobs.append(spec)
|
||||
return self.result
|
||||
|
||||
|
||||
def resolve_sandbox_runner() -> SandboxRunner:
|
||||
"""Return the Docker CLI runner, or a fake when OPENULTRASAST_SANDBOX_RUNNER=fake."""
|
||||
flag = os.environ.get(RUNNER_ENV, "").strip().lower()
|
||||
if flag in _FAKE_RUNNER:
|
||||
return FakeSandboxRunner()
|
||||
return DockerCliRunner()
|
||||
|
||||
|
||||
class DockerCliRunner:
|
||||
"""Run a job with Docker CLI flags that match the threat model."""
|
||||
|
||||
def __init__(self, *, runner: Callable[..., CompletedProcess[str]] | None = None) -> None:
|
||||
self._runner = runner
|
||||
|
||||
def run(self, spec: SandboxJob) -> SandboxResult:
|
||||
if spec.scratch_files:
|
||||
with tempfile.TemporaryDirectory(prefix="ousast-scratch-") as tmp:
|
||||
seed = Path(tmp)
|
||||
_write_scratch_files(seed, spec.scratch_files)
|
||||
return self._invoke(_argv_with_scratch_seed(spec, seed), spec)
|
||||
return self._invoke(build_docker_argv(spec), spec)
|
||||
|
||||
def _invoke(self, argv: list[str], spec: SandboxJob) -> SandboxResult:
|
||||
_reject_unsafe_argv(argv)
|
||||
run = self._runner if self._runner is not None else subprocess.run
|
||||
try:
|
||||
completed = run(
|
||||
argv,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=spec.timeout_seconds,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired as exc:
|
||||
return SandboxResult(
|
||||
exit_code=-1,
|
||||
stdout=_as_text(exc.stdout),
|
||||
stderr=_as_text(exc.stderr),
|
||||
timed_out=True,
|
||||
)
|
||||
return SandboxResult(
|
||||
exit_code=completed.returncode,
|
||||
stdout=completed.stdout or "",
|
||||
stderr=completed.stderr or "",
|
||||
timed_out=False,
|
||||
)
|
||||
|
||||
|
||||
def build_docker_argv(job: SandboxJob) -> list[str]:
|
||||
argv = [*_isolation_argv(job), job.image, *job.command]
|
||||
_reject_unsafe_argv(argv)
|
||||
return argv
|
||||
|
||||
|
||||
def _isolation_argv(job: SandboxJob) -> list[str]:
|
||||
repo = str(job.repo_root.resolve())
|
||||
return [
|
||||
"docker",
|
||||
"run",
|
||||
"--rm",
|
||||
"--init",
|
||||
"--network",
|
||||
"none",
|
||||
"--read-only",
|
||||
"--tmpfs",
|
||||
f"{SCRATCH_MOUNT}:rw,exec",
|
||||
"--memory",
|
||||
f"{job.memory_mb}m",
|
||||
"--pids-limit",
|
||||
str(job.pids_limit),
|
||||
"--cap-drop",
|
||||
"ALL",
|
||||
"--security-opt",
|
||||
"no-new-privileges",
|
||||
"--user",
|
||||
NONROOT_USER,
|
||||
"--mount",
|
||||
f"type=bind,src={repo},dst={WORKSPACE_MOUNT},ro",
|
||||
]
|
||||
|
||||
|
||||
def _argv_with_scratch_seed(job: SandboxJob, seed: Path) -> list[str]:
|
||||
argv = [
|
||||
*_isolation_argv(job),
|
||||
"--mount",
|
||||
f"type=bind,src={seed.resolve()},dst={SCRATCH_SEED_MOUNT},ro",
|
||||
job.image,
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
_COPY_SEED_THEN_EXEC,
|
||||
_SEED_ARGV0,
|
||||
*job.command,
|
||||
]
|
||||
_reject_unsafe_argv(argv)
|
||||
return argv
|
||||
|
||||
|
||||
def _write_scratch_files(root: Path, files: Mapping[str, str]) -> None:
|
||||
resolved_root = root.resolve()
|
||||
for relative, content in files.items():
|
||||
candidate = Path(relative)
|
||||
if candidate.is_absolute():
|
||||
raise ValueError(f"scratch path {relative!r} must be relative to {SCRATCH_MOUNT}")
|
||||
destination = (resolved_root / candidate).resolve()
|
||||
try:
|
||||
common = os.path.commonpath([str(resolved_root), str(destination)])
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"scratch path {relative!r} escapes {SCRATCH_MOUNT}") from exc
|
||||
if common != str(resolved_root):
|
||||
raise ValueError(f"scratch path {relative!r} escapes {SCRATCH_MOUNT}")
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
destination.write_text(content)
|
||||
_chmod_seed_tree(resolved_root)
|
||||
|
||||
|
||||
def _chmod_seed_tree(root: Path) -> None:
|
||||
# Container user 65534 must be able to traverse and read the seed bind.
|
||||
for dirpath, _dirnames, filenames in os.walk(root):
|
||||
Path(dirpath).chmod(0o755)
|
||||
for name in filenames:
|
||||
(Path(dirpath) / name).chmod(0o644)
|
||||
|
||||
|
||||
def _reject_unsafe_argv(argv: list[str]) -> None:
|
||||
joined = " ".join(argv)
|
||||
if "/var/run/docker.sock" in joined:
|
||||
raise ValueError("sandbox argv must not mount the host Docker socket")
|
||||
if "--privileged" in argv:
|
||||
raise ValueError("sandbox argv must not be privileged")
|
||||
for index, item in enumerate(argv):
|
||||
if item in {"--network", "--net"} and index + 1 < len(argv) and argv[index + 1] == "host":
|
||||
raise ValueError("sandbox argv must not use host network")
|
||||
if item in {"--network=host", "--net=host"}:
|
||||
raise ValueError("sandbox argv must not use host network")
|
||||
|
||||
|
||||
def _as_text(value: str | bytes | None) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, bytes):
|
||||
return value.decode(errors="replace")
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user