Skip to content
This repository was archived by the owner on Aug 3, 2026. It is now read-only.
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
44 changes: 44 additions & 0 deletions proof/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
Expand Down Expand Up @@ -272,6 +273,46 @@ def scan_diff_for_restricted(patch_text: str, restricted_patterns: list[str]) ->
return violations


# --- Off-protocol input-injection scan (proof-forgery class) ------------------
#
# A canonical recipe trains FROM SCRATCH and reads only the mounted canonical
# data; it never references a host path or loads pre-trained weights. A miner
# who patches train() to `torch.load("/home/.../checkpoint.pt")` (a model trained
# off-protocol, mounted on their own CC box) or points the data dir at
# `/mnt/.../data_50b` produces a bundle the attested run did NOT legitimately
# train — the attestation still signs because the canonical image DID run the
# recipe; the recipe just loaded a file. These patterns flag that class.
#
# This is a STATIC op1 gate (cheap, before any compute): necessary but NOT
# sufficient — a determined miner can stage inputs under a canonical mount path.
# The authoritative defense is sealed re-eval (re-run with no external mounts +
# require the checkpoint to reproduce). It does stop the current in-the-wild
# `torch.load("/home/.../checkpoint.pt")` + `/mnt/.../data` attempts.
_EXTERNAL_PATH_RE = re.compile(r"""['"`](?:~|\.\.)?/(?:home|root|mnt|media|srv|scratch|Users)\b""")
_WEIGHT_LOAD_RE = re.compile(
r"\b(?:torch\.load|pickle\.loads?|np\.load|numpy\.load|joblib\.load|load_file|safetensors\w*\.\w*load\w*)\s*\("
)
_ABS_WEIGHT_PATH_RE = re.compile(r"""['"`]/[^'"`]*\.(?:pt|pth|ckpt|safetensors|bin|pkl|npz)\b""")


def scan_diff_for_exploit_patterns(patch_text: str) -> list[tuple[str, str]]:
"""Flag ADDED recipe-patch lines that inject off-protocol inputs (the proof-
forgery class: load an externally-trained checkpoint or train on non-canonical
data the attested run never produced). Returns [(reason, offending_line)];
empty list = clean. Both op1 (validator-side, authoritative) and the
in-container runner reject on any hit."""
hits: list[tuple[str, str]] = []
for raw in patch_text.splitlines():
if not raw.startswith("+") or raw.startswith("+++"):
continue
body = raw[1:].strip()
if _EXTERNAL_PATH_RE.search(body):
hits.append(("references an external/host path (off-protocol input injection)", body[:200]))
elif _WEIGHT_LOAD_RE.search(body) and _ABS_WEIGHT_PATH_RE.search(body):
hits.append(("loads model weights from an absolute path (recipe must train from scratch)", body[:200]))
return hits


def apply_patch(workdir: Path, patch_path: Path) -> None:
"""Apply a unified diff using `git apply` (no git repo needed with --3way? No,
we use plain patch). We use `patch -p1` since it's simpler."""
Expand Down Expand Up @@ -342,6 +383,9 @@ def run_proof_test(
violations = scan_diff_for_restricted(patch_text, restricted_patterns)
if violations:
raise RuntimeError(f"patch touches restricted paths: {violations}")
exploit_hits = scan_diff_for_exploit_patterns(patch_text)
if exploit_hits:
raise RuntimeError(f"patch injects off-protocol inputs: {exploit_hits}")

# 3. Create a working copy of the canonical recipe and apply the patch.
# The recipe (model/, recipe/, data/, configs/) lives in the sibling
Expand Down
61 changes: 60 additions & 1 deletion tests/test_restricted_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,70 @@
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

import ralph_bootstrap # noqa: F401
from proof.runner import _extract_diff_paths, scan_diff_for_restricted
from proof.runner import (
_extract_diff_paths,
scan_diff_for_exploit_patterns,
scan_diff_for_restricted,
)

RESTRICTED = ["eval/**", "calibration/**", "validator/**", "proof/**", "restricted_files.yaml"]


# --- scan_diff_for_exploit_patterns: off-protocol input-injection (proof-forgery) ---
def _added(*lines: str) -> str:
return "+++ b/recipe/train.py\n" + "\n".join("+" + ln for ln in lines) + "\n"


def test_warm_start_external_checkpoint_load_flagged():
# PR#586-class: train() loads an off-protocol checkpoint from the miner's box.
hits = scan_diff_for_exploit_patterns(
_added('_ws = torch.load("/home/jovyan/v10sub/checkpoint.pt", weights_only=True)')
)
assert hits and "external/host path" in hits[0][0]


def test_noncanonical_data_path_flagged():
# PR#344-class: a config points the data dir at a host path -> bypasses the lock.
diff = '+++ b/configs/big.json\n+ "data_base_dir": "/mnt/scratch/SN40/data_50b",\n'
assert scan_diff_for_exploit_patterns(diff)


def test_host_toolchain_path_flagged():
assert scan_diff_for_exploit_patterns(_added('_GCC = "/home/root/diony/toolchain/gcc"'))


def test_abs_checkpoint_load_under_canonical_mount_flagged():
# staging weights under a canonical mount still loads a .pt by absolute path.
hits = scan_diff_for_exploit_patterns(_added('sd = torch.load("/data/staged/model.pt")'))
assert hits and "absolute path" in hits[0][0]


def test_legit_lr_schedule_returns_clean():
# the false positives a naive return-scan produced (PR#585 / #413).
diff = _added(
" def lr_at(step):",
" return cfg.min_lr + (cfg.max_lr - cfg.min_lr) * frac",
" return cfg.max_lr",
)
assert scan_diff_for_exploit_patterns(diff) == []


def test_legit_relative_and_canonical_data_clean():
diff = _added(
' shards = sorted(Path("data/shards").glob("*.bin"))',
' manifest = json.load(open("/data/data_manifest.json"))',
' ckpt = torch.load(out_dir / "checkpoint.pt")',
' os.environ.setdefault("TRITON_CACHE_DIR", "/tmp/ralph_triton")',
)
assert scan_diff_for_exploit_patterns(diff) == []


def test_removed_lines_not_scanned():
# a removal (`-`) referencing a host path must not trip the scanner.
diff = '+++ b/recipe/train.py\n- x = torch.load("/home/old/ckpt.pt")\n'
assert scan_diff_for_exploit_patterns(diff) == []


def test_clean_diff_does_not_match():
diff = """diff --git a/configs/h100_default.json b/configs/h100_default.json
--- a/configs/h100_default.json
Expand Down
5 changes: 4 additions & 1 deletion validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from proof.real_attest import (
verify_attestation as verify_real_attestation,
)
from proof.runner import _load_restricted_paths, scan_diff_for_restricted
from proof.runner import _load_restricted_paths, scan_diff_for_exploit_patterns, scan_diff_for_restricted
from proof.sources import compute_container_measurement

# Hard-coded sanity bounds for the miner-submitted model config. The validator
Expand Down Expand Up @@ -293,6 +293,9 @@ def op1_diff_and_integrity(
violations = scan_diff_for_restricted(patch_text, patterns)
if violations:
return False, f"patch touches restricted paths: {violations}"
exploit_hits = scan_diff_for_exploit_patterns(patch_text)
if exploit_hits:
return False, f"patch injects off-protocol inputs: {exploit_hits[0][0]} :: {exploit_hits[0][1]}"

return True, "ok"

Expand Down
Loading