Files
awoooi/scripts/security/verify_external_mcp_replay_receipt.py

899 lines
33 KiB
Python

#!/usr/bin/env python3
"""Independently verify an external MCP protocol/schema replay receipt.
This standalone verifier intentionally does not import the replay controller.
It revalidates the committed policy and artifact receipt chain, rematerializes
the immutable bundle, and repeats initialize plus tools/list in a fresh offline
container. No browser or MCP tool is invoked.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import select
import shutil
import subprocess
import sys
import tarfile
import tempfile
import uuid
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
from typing import Any
POLICY_SCHEMA = "awoooi_external_mcp_replay_policy_v1"
CONTROLLER_SCHEMA = "awoooi_external_mcp_replay_receipt_v1"
VERIFIER_SCHEMA = "awoooi_external_mcp_replay_verifier_receipt_v1"
ARTIFACT_CONTROLLER_SCHEMA = "awoooi_external_mcp_artifact_receipt_v1"
ARTIFACT_VERIFIER_SCHEMA = "awoooi_external_mcp_artifact_verifier_receipt_v1"
ARTIFACT_BUNDLE_SCHEMA = "awoooi_external_mcp_artifact_bundle_v1"
SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
SHA512_PATTERN = re.compile(r"^[0-9a-f]{128}$")
OCI_REF_PATTERN = re.compile(
r"^[a-z0-9.-]+(?::[0-9]+)?/[a-z0-9._/-]+@sha256:[0-9a-f]{64}$"
)
MAX_JSON_BYTES = 4 * 1024 * 1024
MAX_PROTOCOL_LINE_BYTES = 2 * 1024 * 1024
MAX_FILES = 4_096
MAX_UNPACKED_BYTES = 40 * 1024 * 1024
class VerifierError(RuntimeError):
"""Fail-closed public-safe verifier error class."""
def _canonical(payload: Any) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _read_json(path: Path, error: str) -> tuple[bytes, dict[str, Any]]:
try:
raw = path.read_bytes()
except OSError as exc:
raise VerifierError(error) from exc
if not raw or len(raw) > MAX_JSON_BYTES:
raise VerifierError(error)
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
raise VerifierError(error) from exc
if not isinstance(payload, dict):
raise VerifierError(error)
return raw, payload
def _dict(value: Any, error: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise VerifierError(error)
return value
def _text(value: Any, error: str) -> str:
if not isinstance(value, str) or not value.strip():
raise VerifierError(error)
return value.strip()
def _exact_digest(value: Any, error: str, *, prefixed: bool = False) -> str:
text = _text(value, error)
digest = text.removeprefix("sha256:") if prefixed else text
if not SHA256_PATTERN.fullmatch(digest):
raise VerifierError(error)
if prefixed and not text.startswith("sha256:"):
raise VerifierError(error)
return text
def _oci_ref(value: Any, error: str) -> str:
ref = _text(value, error)
if not OCI_REF_PATTERN.fullmatch(ref):
raise VerifierError(error)
return ref
def load_and_validate_policy(path: Path) -> tuple[dict[str, Any], str]:
_, policy = _read_json(path, "policy_unreadable")
if (
policy.get("schema_version") != POLICY_SCHEMA
or policy.get("policy_version") != "1.0.0"
or policy.get("risk_class") != "high"
or policy.get("scope")
!= "offline_protocol_and_tool_schema_compatibility_only"
):
raise VerifierError("policy_contract_invalid")
try:
expires = datetime.fromisoformat(_text(policy.get("expires_at"), "policy_expiry_invalid"))
except ValueError as exc:
raise VerifierError("policy_expiry_invalid") from exc
if expires.tzinfo is None or expires <= datetime.now(timezone.utc):
raise VerifierError("policy_expired")
artifact = _dict(policy.get("artifact_source"), "artifact_source_invalid")
runtime = _dict(policy.get("runtime"), "runtime_invalid")
protocol = _dict(policy.get("protocol_contract"), "protocol_invalid")
boundaries = _dict(policy.get("execution_boundaries"), "boundaries_invalid")
if any(value is not False for value in boundaries.values()):
raise VerifierError("boundary_must_remain_false")
if (
runtime.get("network_mode") != "none"
or runtime.get("read_only_root") is not True
or runtime.get("no_new_privileges") is not True
or runtime.get("cap_drop") != ["ALL"]
or runtime.get("user") != "65534:65534"
or runtime.get("platform") != "linux/amd64"
):
raise VerifierError("runtime_isolation_invalid")
artifact_ref = _oci_ref(artifact.get("artifact_ref"), "artifact_ref_invalid")
runtime_ref = _oci_ref(runtime.get("image_ref"), "runtime_ref_invalid")
if artifact_ref == runtime_ref:
raise VerifierError("artifact_runtime_separation_invalid")
for field in (
"controller_receipt_sha256",
"verifier_receipt_sha256",
"bundle_manifest_sha256",
"cyclonedx_sbom_sha256",
"tool_name_set_sha256",
"tool_schema_manifest_sha256",
):
source = artifact if field in artifact else protocol
_exact_digest(source.get(field), f"{field}_invalid")
_exact_digest(artifact.get("policy_checksum"), "artifact_policy_checksum_invalid", prefixed=True)
integrities = _dict(artifact.get("package_integrities"), "integrities_invalid")
if len(integrities) != 3:
raise VerifierError("integrities_invalid")
for digest in integrities.values():
if not isinstance(digest, str) or not SHA512_PATTERN.fullmatch(digest):
raise VerifierError("integrities_invalid")
allowed = protocol.get("adapter_allowed_tools")
denied = protocol.get("adapter_denied_tools")
if (
not isinstance(allowed, list)
or not isinstance(denied, list)
or len(allowed) != 4
or len(denied) != 20
or set(allowed) & set(denied)
or set(allowed)
!= {
"browser_close",
"browser_navigate",
"browser_snapshot",
"browser_take_screenshot",
}
or "browser_run_code_unsafe" not in denied
or "browser_file_upload" not in denied
):
raise VerifierError("adapter_tool_partition_invalid")
return policy, f"sha256:{_sha256(_canonical(policy))}"
def validate_controller_receipt(
policy: dict[str, Any], policy_checksum: str, path: Path
) -> tuple[dict[str, Any], str]:
raw, receipt = _read_json(path, "controller_receipt_unreadable")
if (
receipt.get("schema_version") != CONTROLLER_SCHEMA
or receipt.get("status")
!= "completed_protocol_schema_replay_pending_independent_verifier"
or receipt.get("mode") != "apply"
or receipt.get("policy_checksum") != policy_checksum
or receipt.get("work_item_id") != policy.get("work_item_id")
or receipt.get("candidate_id") != policy.get("candidate_id")
or receipt.get("adapter_id") != policy.get("adapter_id")
):
raise VerifierError("controller_receipt_terminal_invalid")
try:
normalized_run_id = str(uuid.UUID(_text(receipt.get("run_id"), "run_id_invalid")))
except ValueError as exc:
raise VerifierError("run_id_invalid") from exc
if (
receipt.get("run_id") != normalized_run_id
or receipt.get("trace_id") != f"mcp-replay-{normalized_run_id}"
):
raise VerifierError("run_trace_identity_invalid")
for field in (
"direct_gateway_registration_allowed",
"adapter_registration_allowed",
"browser_started",
"tool_call_performed",
"navigation_performed",
"raw_request_or_response_body_stored",
"external_rag_write_performed",
"production_write_performed",
"shadow_allowed",
"canary_allowed",
"promotion_allowed",
):
if receipt.get(field) is not False:
raise VerifierError("controller_boundary_invalid")
artifact = _dict(policy.get("artifact_source"), "artifact_source_invalid")
runtime = _dict(policy.get("runtime"), "runtime_invalid")
if (
receipt.get("artifact_ref") != artifact.get("artifact_ref")
or receipt.get("runtime_image_ref") != runtime.get("image_ref")
or receipt.get("artifact_audit_commit") != artifact.get("audit_commit")
):
raise VerifierError("controller_source_identity_invalid")
protocol = _dict(policy.get("protocol_contract"), "protocol_invalid")
observed = _dict(receipt.get("protocol_observation"), "protocol_observation_invalid")
expected = {
"protocol_version": protocol.get("protocol_version"),
"server_name": protocol.get("server_name"),
"server_version": protocol.get("server_version"),
"tool_count": protocol.get("tool_count"),
"tool_name_set_sha256": protocol.get("tool_name_set_sha256"),
"tool_schema_manifest_sha256": protocol.get("tool_schema_manifest_sha256"),
}
if any(observed.get(field) != value for field, value in expected.items()):
raise VerifierError("controller_protocol_observation_mismatch")
if observed.get("shutdown_mode") != "stdin_eof_clean_exit":
raise VerifierError("controller_shutdown_terminal_invalid")
stages = _dict(receipt.get("stage_receipts"), "controller_stages_invalid")
rollback = _dict(
stages.get("rollback_or_no_write_terminal"), "controller_rollback_invalid"
)
if (
rollback.get("status")
!= "mcp_process_terminated_and_ephemeral_container_removed"
or rollback.get("ephemeral_container_removed") is not True
or rollback.get("production_route_changed") is not False
):
raise VerifierError("controller_rollback_invalid")
return receipt, _sha256(raw)
def validate_artifact_receipt_chain(
policy: dict[str, Any], controller_path: Path, verifier_path: Path
) -> tuple[str, str]:
artifact = _dict(policy.get("artifact_source"), "artifact_source_invalid")
controller_raw, controller = _read_json(
controller_path, "artifact_controller_receipt_unreadable"
)
verifier_raw, verifier = _read_json(
verifier_path, "artifact_verifier_receipt_unreadable"
)
controller_sha = _sha256(controller_raw)
verifier_sha = _sha256(verifier_raw)
if (
controller_sha != artifact.get("controller_receipt_sha256")
or verifier_sha != artifact.get("verifier_receipt_sha256")
or controller.get("schema_version") != ARTIFACT_CONTROLLER_SCHEMA
or controller.get("status")
!= "completed_internal_mirror_pending_independent_verifier"
or verifier.get("schema_version") != ARTIFACT_VERIFIER_SCHEMA
or verifier.get("status") != "completed_internal_mirror_verified"
or verifier.get("controller_receipt_sha256") != controller_sha
):
raise VerifierError("artifact_receipt_chain_invalid")
post = _dict(verifier.get("independent_post_verifier"), "artifact_verifier_invalid")
if (
post.get("status") != "passed"
or post.get("container_or_mcp_started") is not False
or controller.get("run_id") != verifier.get("run_id")
or controller.get("trace_id") != verifier.get("trace_id")
):
raise VerifierError("artifact_independent_verifier_invalid")
for receipt in (controller, verifier):
if (
receipt.get("internal_mirror_ref") != artifact.get("artifact_ref")
or receipt.get("policy_checksum") != artifact.get("policy_checksum")
or any(
receipt.get(field) is not False
for field in (
"promotion_allowed",
"replay_allowed",
"shadow_allowed",
"canary_allowed",
)
)
):
raise VerifierError("artifact_receipt_boundary_invalid")
return controller_sha, verifier_sha
def _docker() -> str:
executable = shutil.which("docker")
if not executable:
raise VerifierError("docker_cli_unavailable")
return executable
def _run(command: list[str], *, timeout: int, error: str) -> str:
try:
result = subprocess.run(
command,
check=False,
capture_output=True,
text=True,
timeout=timeout,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise VerifierError(error) from exc
if result.returncode != 0:
raise VerifierError(error)
return result.stdout
def _pull_and_readback(docker: str, ref: str, platform: str) -> None:
_run(
[docker, "pull", "--platform", platform, ref],
timeout=300,
error="image_pull_failed",
)
output = _run(
[docker, "image", "inspect", "--format", "{{json .RepoDigests}}", ref],
timeout=30,
error="image_readback_failed",
)
try:
digests = json.loads(output.strip())
except json.JSONDecodeError as exc:
raise VerifierError("image_readback_invalid") from exc
if not isinstance(digests, list) or ref not in digests:
raise VerifierError("image_digest_readback_mismatch")
def _safe_member(name: str) -> Path | None:
pure = PurePosixPath(name)
if pure.is_absolute() or not pure.parts or ".." in pure.parts:
raise VerifierError("archive_path_invalid")
if pure.parts[0] != "package":
raise VerifierError("archive_root_invalid")
if len(pure.parts) == 1:
return None
return Path(*pure.parts[1:])
def _unpack(tarball: Path, destination: Path) -> tuple[int, int]:
files = 0
size = 0
try:
archive = tarfile.open(tarball, "r:gz")
except (OSError, tarfile.TarError) as exc:
raise VerifierError("tarball_invalid") from exc
with archive:
for member in archive.getmembers():
relative = _safe_member(member.name)
if relative is None:
continue
target = destination / relative
if member.isdir():
target.mkdir(parents=True, exist_ok=True)
continue
if not member.isfile() or member.issym() or member.islnk():
raise VerifierError("archive_member_type_invalid")
files += 1
size += member.size
if files > MAX_FILES or size > MAX_UNPACKED_BYTES:
raise VerifierError("archive_limits_exceeded")
source = archive.extractfile(member)
if source is None:
raise VerifierError("archive_member_unreadable")
content = source.read(member.size + 1)
if len(content) != member.size:
raise VerifierError("archive_member_size_mismatch")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
return files, size
def independently_materialize(
policy: dict[str, Any], directory: Path
) -> tuple[Path, int, int]:
artifact = _dict(policy.get("artifact_source"), "artifact_source_invalid")
runtime = _dict(policy.get("runtime"), "runtime_invalid")
docker = _docker()
artifact_ref = _oci_ref(artifact.get("artifact_ref"), "artifact_ref_invalid")
platform = _text(runtime.get("platform"), "platform_invalid")
_pull_and_readback(docker, artifact_ref, platform)
bundle = directory / "bundle"
modules = directory / "node_modules"
bundle.mkdir()
modules.mkdir()
container = f"awoooi-mcp-verifier-artifact-{uuid.uuid4().hex[:16]}"
try:
_run(
[
docker,
"create",
"--platform",
platform,
"--name",
container,
artifact_ref,
"/nonexistent/no-start",
],
timeout=60,
error="artifact_container_create_failed",
)
_run(
[docker, "cp", f"{container}:/artifact/.", str(bundle)],
timeout=60,
error="artifact_copy_failed",
)
finally:
subprocess.run(
[docker, "rm", "-f", container],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=30,
)
manifest_raw, manifest = _read_json(
bundle / "bundle-manifest.json", "bundle_manifest_invalid"
)
sbom_raw, sbom = _read_json(bundle / "sbom.cdx.json", "bundle_sbom_invalid")
if (
_sha256(manifest_raw) != artifact.get("bundle_manifest_sha256")
or _sha256(sbom_raw) != artifact.get("cyclonedx_sbom_sha256")
or manifest.get("schema_version") != ARTIFACT_BUNDLE_SCHEMA
or manifest.get("candidate_id") != policy.get("candidate_id")
or manifest.get("policy_checksum") != artifact.get("policy_checksum")
or sbom.get("bomFormat") != "CycloneDX"
or sbom.get("specVersion") != "1.5"
):
raise VerifierError("bundle_identity_invalid")
bundle_boundaries = _dict(
manifest.get("operation_boundaries"), "bundle_boundaries_invalid"
)
if any(
bundle_boundaries.get(field) is not False
for field in (
"mcp_server_started",
"browser_started",
"external_tool_call_performed",
"external_rag_write_performed",
"production_write_performed",
"runtime_request_or_response_body_stored",
)
):
raise VerifierError("bundle_boundary_invalid")
integrities = _dict(artifact.get("package_integrities"), "integrities_invalid")
rows = manifest.get("packages")
if not isinstance(rows, list) or len(rows) != 3:
raise VerifierError("bundle_package_set_invalid")
observed: set[str] = set()
total_files = 0
total_size = 0
for row in rows:
if not isinstance(row, dict):
raise VerifierError("bundle_package_set_invalid")
name = _text(row.get("name"), "package_name_invalid")
version = _text(row.get("version"), "package_version_invalid")
key = f"{name}@{version}"
tarball = bundle / _text(row.get("tarball_path"), "tarball_path_invalid")
try:
raw = tarball.read_bytes()
except OSError as exc:
raise VerifierError("tarball_unreadable") from exc
if (
key in observed
or integrities.get(key) != row.get("tarball_sha512_hex")
or hashlib.sha512(raw).hexdigest() != integrities.get(key)
):
raise VerifierError("tarball_integrity_mismatch")
observed.add(key)
destination = (
modules / "@playwright" / "mcp"
if name == "@playwright/mcp"
else modules / name
)
destination.mkdir(parents=True)
files, size = _unpack(tarball, destination)
total_files += files
total_size += size
_, package_json = _read_json(destination / "package.json", "package_json_invalid")
if package_json.get("name") != name or package_json.get("version") != version:
raise VerifierError("package_json_identity_mismatch")
if observed != set(integrities):
raise VerifierError("bundle_package_set_mismatch")
for path in modules.rglob("*"):
path.chmod(0o755 if path.is_dir() else 0o444)
modules.chmod(0o755)
directory.chmod(0o755)
return modules, total_files, total_size
def _send(process: subprocess.Popen[str], payload: dict[str, Any]) -> None:
if process.stdin is None:
raise VerifierError("mcp_stdin_unavailable")
try:
process.stdin.write(_canonical(payload).decode("utf-8") + "\n")
process.stdin.flush()
except (BrokenPipeError, OSError) as exc:
raise VerifierError("mcp_write_failed") from exc
def _receive(
process: subprocess.Popen[str], timeout_seconds: int, expected_id: int
) -> dict[str, Any]:
if process.stdout is None:
raise VerifierError("mcp_stdout_unavailable")
ready, _, _ = select.select([process.stdout], [], [], timeout_seconds)
if not ready:
raise VerifierError("mcp_response_timeout")
line = process.stdout.readline(MAX_PROTOCOL_LINE_BYTES + 1)
if not line or len(line.encode("utf-8")) > MAX_PROTOCOL_LINE_BYTES:
raise VerifierError("mcp_response_invalid")
try:
response = json.loads(line)
except json.JSONDecodeError as exc:
raise VerifierError("mcp_response_invalid") from exc
if (
not isinstance(response, dict)
or response.get("jsonrpc") != "2.0"
or response.get("id") != expected_id
or "error" in response
or not isinstance(response.get("result"), dict)
):
raise VerifierError("mcp_response_invalid")
return response["result"]
def _independent_tool_hashes(tools: Any) -> tuple[int, str, str, set[str]]:
if not isinstance(tools, list):
raise VerifierError("tool_list_invalid")
names: list[str] = []
schema_rows: list[dict[str, Any]] = []
for tool in tools:
if not isinstance(tool, dict):
raise VerifierError("tool_schema_invalid")
name = _text(tool.get("name"), "tool_name_invalid")
schema = tool.get("inputSchema")
if not isinstance(schema, dict):
raise VerifierError("tool_schema_invalid")
row: dict[str, Any] = {"name": name, "inputSchema": schema}
if "annotations" in tool:
annotations = tool["annotations"]
if not isinstance(annotations, dict):
raise VerifierError("tool_annotations_invalid")
row["annotations"] = annotations
names.append(name)
schema_rows.append(row)
if len(names) != len(set(names)):
raise VerifierError("tool_names_not_unique")
schema_rows.sort(key=lambda row: row["name"])
return (
len(tools),
_sha256(_canonical(sorted(names))),
_sha256(_canonical(schema_rows)),
set(names),
)
def independently_replay(
policy: dict[str, Any], modules: Path
) -> dict[str, Any]:
runtime = _dict(policy.get("runtime"), "runtime_invalid")
protocol = _dict(policy.get("protocol_contract"), "protocol_invalid")
docker = _docker()
runtime_ref = _oci_ref(runtime.get("image_ref"), "runtime_ref_invalid")
platform = _text(runtime.get("platform"), "platform_invalid")
_pull_and_readback(docker, runtime_ref, platform)
container = f"awoooi-mcp-replay-verifier-{uuid.uuid4().hex[:16]}"
command = [
docker,
"run",
"--rm",
"--platform",
platform,
"--name",
container,
"--network",
"none",
"--read-only",
"--security-opt",
"no-new-privileges",
"--cap-drop",
"ALL",
"--pids-limit",
str(runtime.get("pids_limit")),
"--memory",
_text(runtime.get("memory_limit"), "memory_limit_invalid"),
"--cpus",
_text(runtime.get("cpu_limit"), "cpu_limit_invalid"),
"--user",
_text(runtime.get("user"), "runtime_user_invalid"),
"--env",
"HOME=/tmp",
"--env",
"CI=1",
"--tmpfs",
_text(runtime.get("tmpfs"), "tmpfs_invalid"),
"--stop-timeout",
str(runtime.get("shutdown_timeout_seconds")),
"-i",
"-v",
f"{modules.resolve()}:/work/node_modules:ro",
"-w",
"/work",
runtime_ref,
*runtime.get("entrypoint", []),
*runtime.get("arguments", []),
]
stderr_file = tempfile.TemporaryFile(mode="w+t", encoding="utf-8")
process: subprocess.Popen[str] | None = None
try:
try:
process = subprocess.Popen(
command,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=stderr_file,
text=True,
bufsize=1,
env={**os.environ, "DOCKER_CLI_HINTS": "false"},
)
except OSError as exc:
raise VerifierError("mcp_process_start_failed") from exc
_send(
process,
{
"jsonrpc": "2.0",
"id": 41,
"method": "initialize",
"params": {
"protocolVersion": protocol.get("protocol_version"),
"capabilities": {},
"clientInfo": {
"name": "awoooi-external-mcp-replay-verifier",
"version": "1",
},
},
},
)
timeout = int(runtime.get("startup_timeout_seconds"))
initialized = _receive(process, timeout, 41)
server = _dict(initialized.get("serverInfo"), "server_info_invalid")
if (
initialized.get("protocolVersion") != protocol.get("protocol_version")
or server.get("name") != protocol.get("server_name")
or server.get("version") != protocol.get("server_version")
):
raise VerifierError("initialize_contract_mismatch")
_send(
process,
{
"jsonrpc": "2.0",
"method": "notifications/initialized",
"params": {},
},
)
_send(
process,
{"jsonrpc": "2.0", "id": 42, "method": "tools/list", "params": {}},
)
listed = _receive(process, timeout, 42)
count, names_hash, schemas_hash, names = _independent_tool_hashes(
listed.get("tools")
)
if (
count != protocol.get("tool_count")
or names_hash != protocol.get("tool_name_set_sha256")
or schemas_hash != protocol.get("tool_schema_manifest_sha256")
or names
!= set(protocol.get("adapter_allowed_tools", []))
| set(protocol.get("adapter_denied_tools", []))
):
raise VerifierError("tool_contract_mismatch")
if process.stdin is None:
raise VerifierError("mcp_stdin_unavailable")
process.stdin.close()
try:
exit_code = process.wait(timeout=int(runtime.get("shutdown_timeout_seconds")))
except subprocess.TimeoutExpired as exc:
raise VerifierError("mcp_shutdown_timeout") from exc
if exit_code != 0:
raise VerifierError("mcp_exit_invalid")
finally:
if process is not None and process.poll() is None:
process.terminate()
try:
process.wait(timeout=int(runtime.get("shutdown_timeout_seconds", 5)))
except subprocess.TimeoutExpired:
process.kill()
process.wait(timeout=5)
subprocess.run(
[docker, "rm", "-f", container],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=30,
)
stderr_file.close()
inspect = subprocess.run(
[docker, "container", "inspect", container],
check=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
timeout=30,
)
if inspect.returncode == 0:
raise VerifierError("container_cleanup_failed")
return {
"protocol_version": protocol.get("protocol_version"),
"server_name": protocol.get("server_name"),
"server_version": protocol.get("server_version"),
"tool_count": count,
"tool_name_set_sha256": names_hash,
"tool_schema_manifest_sha256": schemas_hash,
"process_exit_code": 0,
"shutdown_mode": "stdin_eof_clean_exit",
"ephemeral_container_removed": True,
}
def build_verifier_receipt(
*,
policy: dict[str, Any],
policy_checksum: str,
controller: dict[str, Any],
controller_sha256: str,
artifact_controller_sha256: str,
artifact_verifier_sha256: str,
observation: dict[str, Any],
package_file_count: int,
package_unpacked_bytes: int,
) -> dict[str, Any]:
artifact = _dict(policy.get("artifact_source"), "artifact_source_invalid")
runtime = _dict(policy.get("runtime"), "runtime_invalid")
return {
"schema_version": VERIFIER_SCHEMA,
"run_id": controller["run_id"],
"trace_id": controller["trace_id"],
"work_item_id": controller["work_item_id"],
"candidate_id": controller["candidate_id"],
"adapter_id": controller["adapter_id"],
"risk_class": "high",
"status": "completed_protocol_schema_replay_verified",
"observed_at": datetime.now(timezone.utc).isoformat(),
"policy_checksum": policy_checksum,
"controller_receipt_sha256": controller_sha256,
"artifact_controller_receipt_sha256": artifact_controller_sha256,
"artifact_verifier_receipt_sha256": artifact_verifier_sha256,
"artifact_ref": artifact.get("artifact_ref"),
"runtime_image_ref": runtime.get("image_ref"),
"independent_post_verifier": {
"status": "passed",
"implementation": "standalone_no_controller_import",
"fresh_artifact_digest_readback": True,
"fresh_runtime_digest_readback": True,
"fresh_protocol_replay": True,
"package_file_count": package_file_count,
"package_unpacked_bytes": package_unpacked_bytes,
**observation,
},
"direct_gateway_registration_allowed": False,
"adapter_registration_allowed": False,
"browser_started": False,
"tool_call_performed": False,
"navigation_performed": False,
"raw_request_or_response_body_stored": False,
"external_rag_write_performed": False,
"production_write_performed": False,
"shadow_allowed": False,
"canary_allowed": False,
"promotion_allowed": False,
"promotion_blockers": policy.get("promotion_blockers_after_replay"),
"rollback_terminal": {
"status": "mcp_process_terminated_and_ephemeral_container_removed",
"candidate_registration_state": "disabled",
"ephemeral_container_removed": True,
"production_route_changed": False,
},
"learning_writeback": {
"status": "verified_receipt_ready_for_gitea_audit_branch_writeback",
"external_rag_write_performed": False,
"km_write_performed": False,
},
}
def _write(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(_canonical(payload) + b"\n")
def _failure_receipt(
policy: dict[str, Any], controller: dict[str, Any], error: str
) -> dict[str, Any]:
return {
"schema_version": VERIFIER_SCHEMA,
"run_id": controller.get("run_id"),
"trace_id": controller.get("trace_id"),
"work_item_id": policy.get("work_item_id"),
"candidate_id": policy.get("candidate_id"),
"status": "blocked_with_safe_next_action",
"error_class": error,
"observed_at": datetime.now(timezone.utc).isoformat(),
"browser_started": False,
"tool_call_performed": False,
"external_rag_write_performed": False,
"production_write_performed": False,
"promotion_allowed": False,
"rollback_terminal": {
"status": "candidate_disabled_ephemeral_process_cleanup_attempted",
"candidate_registration_state": "disabled",
"production_route_changed": False,
},
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--policy", type=Path, required=True)
parser.add_argument("--artifact-controller-receipt", type=Path, required=True)
parser.add_argument("--artifact-verifier-receipt", type=Path, required=True)
parser.add_argument("--controller-receipt", type=Path, required=True)
parser.add_argument("--verifier-receipt", type=Path, required=True)
return parser
def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
policy, policy_checksum = load_and_validate_policy(args.policy)
controller: dict[str, Any] = {}
try:
controller, controller_sha256 = validate_controller_receipt(
policy, policy_checksum, args.controller_receipt
)
artifact_controller_sha256, artifact_verifier_sha256 = (
validate_artifact_receipt_chain(
policy,
args.artifact_controller_receipt,
args.artifact_verifier_receipt,
)
)
with tempfile.TemporaryDirectory(prefix="awoooi-mcp-replay-verifier-") as temp:
modules, file_count, unpacked_bytes = independently_materialize(
policy, Path(temp)
)
observation = independently_replay(policy, modules)
receipt = build_verifier_receipt(
policy=policy,
policy_checksum=policy_checksum,
controller=controller,
controller_sha256=controller_sha256,
artifact_controller_sha256=artifact_controller_sha256,
artifact_verifier_sha256=artifact_verifier_sha256,
observation=observation,
package_file_count=file_count,
package_unpacked_bytes=unpacked_bytes,
)
except VerifierError as exc:
_write(args.verifier_receipt, _failure_receipt(policy, controller, str(exc)))
raise
_write(args.verifier_receipt, receipt)
print(json.dumps(receipt, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except VerifierError as exc:
print(
json.dumps(
{
"schema_version": VERIFIER_SCHEMA,
"status": "blocked_with_safe_next_action",
"error_class": str(exc),
},
sort_keys=True,
),
file=sys.stderr,
)
raise SystemExit(1) from None