"""Off-grid proof: run a full playthrough with a socket guard and assert that no non-loopback network connection is attempted. python scripts/net_audit.py Exits non-zero if any outbound connection is made. This backs the "Off the Grid" claim: the served game (case load, interrogation, evidence, accusation, and the on-device voice) reaches the network zero times. """ from __future__ import annotations import contextlib import os import socket import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) # Mirror the deployed container: weights are baked in, analytics off, hub offline - # any accidental download would surface as a violation rather than silently succeed. os.environ.setdefault("HF_HUB_OFFLINE", "1") os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False") _violations: list[str] = [] _orig_connect = socket.socket.connect def _is_loopback(host: str) -> bool: return host in {"127.0.0.1", "::1", "localhost"} or host.startswith("127.") def _guard(self, address): # type: ignore[no-untyped-def] host = address[0] if isinstance(address, tuple) else str(address) if not _is_loopback(str(host)): _violations.append(str(host)) raise OSError(f"net_audit: blocked non-loopback connect to {host}") return _orig_connect(self, address) def main() -> int: socket.socket.connect = _guard # type: ignore[method-assign] from starlette.testclient import TestClient from case_zero.api import build_server client = TestClient(build_server()) # A full play loop over the golden case - load, interrogate, present the breaking # exhibit, read a hint, then accuse - all server-authoritative, no network. run = client.post("/api/case", json={"caseId": "GRAYMOOR-3107"}).json()["runId"] client.post(f"/api/run/{run}/interrogate/iris", json={"questionId": "q3"}) client.post(f"/api/run/{run}/interrogate/iris", json={"presentEvidenceId": "keycard"}) client.get(f"/api/run/{run}/hint", params={"screen": "interro"}) client.post( f"/api/run/{run}/accuse", json={ "suspectId": "iris", "motiveId": "m_credit", "evidenceIds": ["keycard", "voicemail", "cctv"], }, ) # Exercise the on-device voice path too (local Supertonic ONNX). Best-effort: a missing # voice model is fine; a *network* attempt would already be recorded as a violation. with contextlib.suppress(Exception): client.post(f"/api/run/{run}/tts/iris", json={"text": "I was in the library all night."}) if _violations: print(f"FAIL: {len(_violations)} outbound connection(s): {sorted(set(_violations))}") return 1 print("PASS: full playthrough made zero non-loopback connections (Off the Grid verified).") return 0 if __name__ == "__main__": raise SystemExit(main())