feat(mcp): add isolated protocol replay verifier

This commit is contained in:
ogt
2026-07-15 11:01:40 +08:00
parent e01db7391e
commit 7e098d8f66
9 changed files with 3376 additions and 6 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,372 @@
#!/usr/bin/env python3
"""Tests for the bounded external MCP replay controller without mocks."""
from __future__ import annotations
import hashlib
import importlib.util
import io
import json
import sys
import tarfile
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
CONTROLLER_PATH = ROOT / "scripts/security/external_mcp_replay_controller.py"
POLICY_PATH = ROOT / "config/mcp/playwright-mcp-replay-policy.json"
ARTIFACT_POLICY_PATH = ROOT / "config/mcp/playwright-mcp-artifact-policy.json"
WORKFLOW_PATH = ROOT / ".gitea/workflows/mcp-external-replay.yaml"
SPEC = importlib.util.spec_from_file_location("external_mcp_replay_controller", CONTROLLER_PATH)
assert SPEC and SPEC.loader
controller = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = controller
SPEC.loader.exec_module(controller)
def _canonical(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _write_json(path: Path, payload: object) -> bytes:
raw = _canonical(payload) + b"\n"
path.write_bytes(raw)
return raw
def _policy_payload() -> dict:
return json.loads(POLICY_PATH.read_text(encoding="utf-8"))
def _artifact_receipts(policy_payload: dict) -> tuple[dict, dict]:
artifact = policy_payload["artifact_source"]
artifact_run_id = "11111111-2222-4333-8444-555555555555"
artifact_trace_id = f"mcp-artifact-{artifact_run_id}"
packages = []
for key, digest in artifact["package_integrities"].items():
name, version = key.rsplit("@", 1)
packages.append(
{
"name": name,
"version": version,
"tarball_sha512_hex": digest,
}
)
artifact_controller = {
"schema_version": "awoooi_external_mcp_artifact_receipt_v1",
"status": "completed_internal_mirror_pending_independent_verifier",
"run_id": artifact_run_id,
"trace_id": artifact_trace_id,
"promotion_allowed": False,
"replay_allowed": False,
"shadow_allowed": False,
"canary_allowed": False,
"internal_mirror_ref": artifact["artifact_ref"],
"runtime_mirror_ref": artifact["artifact_runtime_ref"],
"policy_checksum": artifact["policy_checksum"],
"bundle_manifest_sha256": artifact["bundle_manifest_sha256"],
"cyclonedx_sbom_sha256": artifact["cyclonedx_sbom_sha256"],
"packages": packages,
}
artifact_verifier = {
"schema_version": "awoooi_external_mcp_artifact_verifier_receipt_v1",
"status": "completed_internal_mirror_verified",
"run_id": artifact_run_id,
"trace_id": artifact_trace_id,
"promotion_allowed": False,
"replay_allowed": False,
"shadow_allowed": False,
"canary_allowed": False,
"internal_mirror_ref": artifact["artifact_ref"],
"runtime_mirror_ref": artifact["artifact_runtime_ref"],
"policy_checksum": artifact["policy_checksum"],
"independent_post_verifier": {"status": "passed"},
}
return artifact_controller, artifact_verifier
def _source_fixture(directory: Path) -> tuple[Path, Path, Path]:
payload = _policy_payload()
artifact_controller, artifact_verifier = _artifact_receipts(payload)
controller_path = directory / "artifact-controller.json"
verifier_path = directory / "artifact-verifier.json"
controller_raw = _write_json(controller_path, artifact_controller)
artifact_verifier["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
verifier_raw = _write_json(verifier_path, artifact_verifier)
payload["artifact_source"]["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
payload["artifact_source"]["verifier_receipt_sha256"] = hashlib.sha256(
verifier_raw
).hexdigest()
policy_path = directory / "replay-policy.json"
_write_json(policy_path, payload)
return policy_path, controller_path, verifier_path
class ReplayPolicyTests(unittest.TestCase):
def _mutated_policy(self, mutate) -> Path:
self.temp = tempfile.TemporaryDirectory()
path = Path(self.temp.name) / "policy.json"
payload = _policy_payload()
mutate(payload)
_write_json(path, payload)
return path
def tearDown(self) -> None:
if hasattr(self, "temp"):
self.temp.cleanup()
def test_committed_policy_is_exact_and_minimal(self) -> None:
policy = controller.load_policy(POLICY_PATH)
self.assertEqual(policy.work_item_id, "MCP-REPLAY-PLAYWRIGHT-001")
self.assertEqual(policy.risk_class, "high")
self.assertEqual(policy.tool_count, 24)
self.assertEqual(len(policy.adapter_allowed_tools), 4)
self.assertEqual(len(policy.adapter_denied_tools), 20)
self.assertRegex(policy.artifact_ref, r"@sha256:[0-9a-f]{64}$")
self.assertRegex(policy.runtime_image_ref, r"@sha256:[0-9a-f]{64}$")
def test_expired_policy_fails_closed(self) -> None:
path = self._mutated_policy(
lambda payload: payload.__setitem__(
"expires_at", "2026-07-01T00:00:00+08:00"
)
)
with self.assertRaisesRegex(controller.ReplayError, "replay_policy_expired"):
controller.load_policy(path)
def test_network_must_remain_none(self) -> None:
path = self._mutated_policy(
lambda payload: payload["runtime"].__setitem__("network_mode", "bridge")
)
with self.assertRaisesRegex(controller.ReplayError, "runtime_isolation_invalid"):
controller.load_policy(path)
def test_runtime_must_remain_non_root(self) -> None:
path = self._mutated_policy(
lambda payload: payload["runtime"].__setitem__("user", "0:0")
)
with self.assertRaisesRegex(controller.ReplayError, "runtime_user_invalid"):
controller.load_policy(path)
def test_floating_runtime_reference_is_rejected(self) -> None:
path = self._mutated_policy(
lambda payload: payload["runtime"].__setitem__(
"image_ref", "registry.wooo.work/awoooi/ci-runner:current"
)
)
with self.assertRaisesRegex(controller.ReplayError, "runtime_image_ref_invalid"):
controller.load_policy(path)
def test_direct_registration_cannot_be_enabled(self) -> None:
path = self._mutated_policy(
lambda payload: payload["execution_boundaries"].__setitem__(
"direct_gateway_registration_allowed", True
)
)
with self.assertRaisesRegex(controller.ReplayError, "execution_boundary_invalid"):
controller.load_policy(path)
def test_dangerous_tool_cannot_leave_denied_partition(self) -> None:
def mutate(payload: dict) -> None:
payload["protocol_contract"]["adapter_denied_tools"].remove(
"browser_run_code_unsafe"
)
path = self._mutated_policy(mutate)
with self.assertRaises(controller.ReplayError):
controller.load_policy(path)
class SourceEvidenceTests(unittest.TestCase):
def test_verified_source_receipt_chain_passes(self) -> None:
with tempfile.TemporaryDirectory() as temp:
policy_path, controller_path, verifier_path = _source_fixture(Path(temp))
policy = controller.load_policy(policy_path)
evidence = controller.validate_source_evidence(
policy,
artifact_policy_path=ARTIFACT_POLICY_PATH,
controller_receipt_path=controller_path,
verifier_receipt_path=verifier_path,
)
self.assertEqual(
evidence.artifact_run_id, "11111111-2222-4333-8444-555555555555"
)
self.assertRegex(evidence.artifact_verifier_receipt_sha256, r"^[0-9a-f]{64}$")
def test_tampered_controller_receipt_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
policy_path, controller_path, verifier_path = _source_fixture(Path(temp))
policy = controller.load_policy(policy_path)
controller_path.write_bytes(controller_path.read_bytes() + b" ")
with self.assertRaisesRegex(
controller.ReplayError,
"artifact_controller_receipt_checksum_mismatch",
):
controller.validate_source_evidence(
policy,
artifact_policy_path=ARTIFACT_POLICY_PATH,
controller_receipt_path=controller_path,
verifier_receipt_path=verifier_path,
)
def test_artifact_policy_checksum_drift_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
policy_path, controller_path, verifier_path = _source_fixture(root)
artifact_policy = json.loads(ARTIFACT_POLICY_PATH.read_text())
artifact_policy["risk_level"] = "medium"
drift_path = root / "artifact-policy.json"
_write_json(drift_path, artifact_policy)
with self.assertRaisesRegex(
controller.ReplayError, "artifact_policy_checksum_mismatch"
):
controller.validate_source_evidence(
controller.load_policy(policy_path),
artifact_policy_path=drift_path,
controller_receipt_path=controller_path,
verifier_receipt_path=verifier_path,
)
class ArchiveAndProtocolTests(unittest.TestCase):
def test_archive_traversal_is_rejected(self) -> None:
with self.assertRaisesRegex(controller.ReplayError, "artifact_archive_path_invalid"):
controller._safe_relative_member("package/../../escape")
def test_archive_symlink_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
tarball = root / "bad.tgz"
with tarfile.open(tarball, "w:gz") as archive:
member = tarfile.TarInfo("package/link")
member.type = tarfile.SYMTYPE
member.linkname = "/etc/passwd"
archive.addfile(member)
with self.assertRaisesRegex(
controller.ReplayError, "artifact_archive_member_type_invalid"
):
controller._extract_package(tarball, root / "out")
def test_regular_archive_extracts_with_exact_size(self) -> None:
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
tarball = root / "good.tgz"
content = b'{"name":"fixture"}'
with tarfile.open(tarball, "w:gz") as archive:
member = tarfile.TarInfo("package/package.json")
member.size = len(content)
archive.addfile(member, io.BytesIO(content))
files, size = controller._extract_package(tarball, root / "out")
self.assertEqual((files, size), (1, len(content)))
self.assertEqual((root / "out/package.json").read_bytes(), content)
def test_tool_hashes_are_order_independent(self) -> None:
tools = [
{
"name": "beta",
"inputSchema": {"type": "object"},
"annotations": {"readOnlyHint": True},
},
{"name": "alpha", "inputSchema": {"type": "object"}},
]
first = controller._tool_hashes(tools)
second = controller._tool_hashes(list(reversed(tools)))
self.assertEqual(first, second)
self.assertEqual(first[0], 2)
self.assertEqual(first[3], ("alpha", "beta"))
def test_duplicate_tool_names_are_rejected(self) -> None:
tools = [
{"name": "same", "inputSchema": {}},
{"name": "same", "inputSchema": {}},
]
with self.assertRaisesRegex(controller.ReplayError, "mcp_tool_names_not_unique"):
controller._tool_hashes(tools)
class ReceiptAndWorkflowTests(unittest.TestCase):
def test_success_receipt_preserves_no_browser_no_write_boundary(self) -> None:
policy = controller.load_policy(POLICY_PATH)
source = controller.SourceEvidence("a", "b", "c", "d")
observation = controller.ReplayObservation(
policy.server_name,
policy.server_version,
policy.protocol_version,
policy.tool_count,
policy.tool_name_set_sha256,
policy.tool_schema_manifest_sha256,
0,
"stdin_eof_clean_exit",
True,
)
receipt = controller.build_receipt(
policy=policy,
source=source,
run_id="11111111-2222-4333-8444-555555555555",
trace_id="mcp-replay-11111111-2222-4333-8444-555555555555",
mode="apply",
observation=observation,
package_file_count=10,
package_unpacked_bytes=20,
)
self.assertEqual(
receipt["status"],
"completed_protocol_schema_replay_pending_independent_verifier",
)
for field in (
"browser_started",
"tool_call_performed",
"external_rag_write_performed",
"production_write_performed",
"promotion_allowed",
):
self.assertIs(receipt[field], False)
def test_failure_receipt_keeps_candidate_disabled(self) -> None:
policy = controller.load_policy(POLICY_PATH)
receipt = controller._failure_receipt(
policy=policy,
run_id="11111111-2222-4333-8444-555555555555",
trace_id="mcp-replay-11111111-2222-4333-8444-555555555555",
error_class="fixture_failure",
)
self.assertEqual(receipt["status"], "blocked_with_safe_next_action")
terminal = receipt["stage_receipts"]["rollback_or_no_write_terminal"]
self.assertEqual(terminal["candidate_registration_state"], "disabled")
self.assertIs(receipt["promotion_allowed"], False)
def test_gitea_workflow_has_no_floating_or_external_source_controls(self) -> None:
raw = WORKFLOW_PATH.read_text(encoding="utf-8").lower()
forbidden = (
"uses" + ":",
"actions" + "/checkout",
"github" + ".com",
"api" + ".github" + ".com",
"raw" + ".githubusercontent" + ".com",
"codeload" + ".github" + ".com",
"ghcr" + ".io",
"npx" + " -y",
"@" + "latest",
":" + "latest",
)
self.assertFalse([value for value in forbidden if value in raw])
self.assertIn("runs-on: awoooi-non110-host", raw)
self.assertIn("wait-host-web-build-pressure.sh", raw)
self.assertIn("mcp-replay-receipts", raw)
self.assertIn("verify_external_mcp_replay_receipt.py", raw)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python3
"""Tests for the standalone external MCP replay verifier without mocks."""
from __future__ import annotations
import hashlib
import importlib.util
import json
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
VERIFIER_PATH = ROOT / "scripts/security/verify_external_mcp_replay_receipt.py"
CONTROLLER_PATH = ROOT / "scripts/security/external_mcp_replay_controller.py"
POLICY_PATH = ROOT / "config/mcp/playwright-mcp-replay-policy.json"
def _load(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
verifier = _load("verify_external_mcp_replay_receipt", VERIFIER_PATH)
controller = _load("external_mcp_replay_controller_for_verifier_test", CONTROLLER_PATH)
def _canonical(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _write(path: Path, payload: object) -> bytes:
raw = _canonical(payload) + b"\n"
path.write_bytes(raw)
return raw
def _policy_payload() -> dict:
return json.loads(POLICY_PATH.read_text(encoding="utf-8"))
def _valid_controller_receipt() -> tuple[dict, dict, str]:
policy = controller.load_policy(POLICY_PATH)
source = controller.SourceEvidence(
artifact_run_id="aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
artifact_trace_id="mcp-artifact-aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
artifact_controller_receipt_sha256="a" * 64,
artifact_verifier_receipt_sha256="b" * 64,
)
observation = controller.ReplayObservation(
server_name=policy.server_name,
server_version=policy.server_version,
protocol_version=policy.protocol_version,
tool_count=policy.tool_count,
tool_name_set_sha256=policy.tool_name_set_sha256,
tool_schema_manifest_sha256=policy.tool_schema_manifest_sha256,
process_exit_code=0,
shutdown_mode="stdin_eof_clean_exit",
ephemeral_container_removed=True,
)
receipt = controller.build_receipt(
policy=policy,
source=source,
run_id="11111111-2222-4333-8444-555555555555",
trace_id="mcp-replay-11111111-2222-4333-8444-555555555555",
mode="apply",
observation=observation,
package_file_count=173,
package_unpacked_bytes=1_000,
)
payload, checksum = verifier.load_and_validate_policy(POLICY_PATH)
return payload, receipt, checksum
def _artifact_chain(policy: dict) -> tuple[dict, dict]:
artifact = policy["artifact_source"]
run_id = "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee"
controller_receipt = {
"schema_version": "awoooi_external_mcp_artifact_receipt_v1",
"status": "completed_internal_mirror_pending_independent_verifier",
"run_id": run_id,
"trace_id": f"mcp-artifact-{run_id}",
"internal_mirror_ref": artifact["artifact_ref"],
"policy_checksum": artifact["policy_checksum"],
"promotion_allowed": False,
"replay_allowed": False,
"shadow_allowed": False,
"canary_allowed": False,
}
verifier_receipt = {
"schema_version": "awoooi_external_mcp_artifact_verifier_receipt_v1",
"status": "completed_internal_mirror_verified",
"run_id": run_id,
"trace_id": f"mcp-artifact-{run_id}",
"internal_mirror_ref": artifact["artifact_ref"],
"policy_checksum": artifact["policy_checksum"],
"promotion_allowed": False,
"replay_allowed": False,
"shadow_allowed": False,
"canary_allowed": False,
"independent_post_verifier": {
"status": "passed",
"container_or_mcp_started": False,
},
}
return controller_receipt, verifier_receipt
class StandalonePolicyTests(unittest.TestCase):
def test_committed_policy_passes_independent_validation(self) -> None:
policy, checksum = verifier.load_and_validate_policy(POLICY_PATH)
self.assertEqual(policy["work_item_id"], "MCP-REPLAY-PLAYWRIGHT-001")
self.assertRegex(checksum, r"^sha256:[0-9a-f]{64}$")
def test_any_enabled_execution_boundary_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
payload = _policy_payload()
payload["execution_boundaries"]["browser_start_allowed"] = True
path = Path(temp) / "policy.json"
_write(path, payload)
with self.assertRaisesRegex(verifier.VerifierError, "boundary_must_remain_false"):
verifier.load_and_validate_policy(path)
def test_adapter_tool_partition_overlap_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
payload = _policy_payload()
payload["protocol_contract"]["adapter_denied_tools"].append(
"browser_snapshot"
)
path = Path(temp) / "policy.json"
_write(path, payload)
with self.assertRaisesRegex(verifier.VerifierError, "adapter_tool_partition_invalid"):
verifier.load_and_validate_policy(path)
def test_tagged_artifact_reference_is_rejected(self) -> None:
with tempfile.TemporaryDirectory() as temp:
payload = _policy_payload()
payload["artifact_source"]["artifact_ref"] = (
"registry.wooo.work/awoooi/mcp-artifacts/playwright-mcp:0.0.78"
)
path = Path(temp) / "policy.json"
_write(path, payload)
with self.assertRaisesRegex(verifier.VerifierError, "artifact_ref_invalid"):
verifier.load_and_validate_policy(path)
class ControllerReceiptTests(unittest.TestCase):
def test_valid_controller_receipt_passes_standalone_validation(self) -> None:
policy, receipt, checksum = _valid_controller_receipt()
with tempfile.TemporaryDirectory() as temp:
path = Path(temp) / "controller.json"
raw = _write(path, receipt)
observed, observed_sha = verifier.validate_controller_receipt(
policy, checksum, path
)
self.assertEqual(observed["run_id"], receipt["run_id"])
self.assertEqual(observed_sha, hashlib.sha256(raw).hexdigest())
def test_controller_browser_start_is_rejected(self) -> None:
policy, receipt, checksum = _valid_controller_receipt()
receipt["browser_started"] = True
with tempfile.TemporaryDirectory() as temp:
path = Path(temp) / "controller.json"
_write(path, receipt)
with self.assertRaisesRegex(verifier.VerifierError, "controller_boundary_invalid"):
verifier.validate_controller_receipt(policy, checksum, path)
def test_controller_missing_cleanup_terminal_is_rejected(self) -> None:
policy, receipt, checksum = _valid_controller_receipt()
receipt["stage_receipts"]["rollback_or_no_write_terminal"][
"ephemeral_container_removed"
] = False
with tempfile.TemporaryDirectory() as temp:
path = Path(temp) / "controller.json"
_write(path, receipt)
with self.assertRaisesRegex(verifier.VerifierError, "controller_rollback_invalid"):
verifier.validate_controller_receipt(policy, checksum, path)
def test_controller_protocol_hash_drift_is_rejected(self) -> None:
policy, receipt, checksum = _valid_controller_receipt()
receipt["protocol_observation"]["tool_name_set_sha256"] = "0" * 64
with tempfile.TemporaryDirectory() as temp:
path = Path(temp) / "controller.json"
_write(path, receipt)
with self.assertRaisesRegex(
verifier.VerifierError, "controller_protocol_observation_mismatch"
):
verifier.validate_controller_receipt(policy, checksum, path)
class ArtifactChainAndToolTests(unittest.TestCase):
def test_valid_artifact_chain_passes(self) -> None:
policy = _policy_payload()
artifact = policy["artifact_source"]
artifact_controller, artifact_verifier = _artifact_chain(policy)
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
controller_path = root / "artifact-controller.json"
verifier_path = root / "artifact-verifier.json"
controller_raw = _write(controller_path, artifact_controller)
artifact_verifier["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
verifier_raw = _write(verifier_path, artifact_verifier)
artifact["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
artifact["verifier_receipt_sha256"] = hashlib.sha256(
verifier_raw
).hexdigest()
observed = verifier.validate_artifact_receipt_chain(
policy, controller_path, verifier_path
)
self.assertEqual(observed[0], artifact["controller_receipt_sha256"])
self.assertEqual(observed[1], artifact["verifier_receipt_sha256"])
def test_artifact_chain_tamper_is_rejected(self) -> None:
policy = _policy_payload()
artifact = policy["artifact_source"]
artifact_controller, artifact_verifier = _artifact_chain(policy)
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
controller_path = root / "artifact-controller.json"
verifier_path = root / "artifact-verifier.json"
controller_raw = _write(controller_path, artifact_controller)
artifact_verifier["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
verifier_raw = _write(verifier_path, artifact_verifier)
artifact["controller_receipt_sha256"] = hashlib.sha256(
controller_raw
).hexdigest()
artifact["verifier_receipt_sha256"] = hashlib.sha256(
verifier_raw
).hexdigest()
verifier_path.write_bytes(verifier_raw + b" ")
with self.assertRaisesRegex(verifier.VerifierError, "artifact_receipt_chain_invalid"):
verifier.validate_artifact_receipt_chain(
policy, controller_path, verifier_path
)
def test_independent_tool_hashes_are_order_independent(self) -> None:
tools = [
{"name": "zeta", "inputSchema": {"type": "object"}},
{
"name": "alpha",
"inputSchema": {"type": "object"},
"annotations": {"readOnlyHint": True},
},
]
first = verifier._independent_tool_hashes(tools)
second = verifier._independent_tool_hashes(list(reversed(tools)))
self.assertEqual(first, second)
self.assertEqual(first[3], {"alpha", "zeta"})
def test_duplicate_tool_names_are_rejected(self) -> None:
tools = [
{"name": "duplicate", "inputSchema": {}},
{"name": "duplicate", "inputSchema": {}},
]
with self.assertRaisesRegex(verifier.VerifierError, "tool_names_not_unique"):
verifier._independent_tool_hashes(tools)
class VerifierReceiptTests(unittest.TestCase):
def test_completed_verifier_receipt_keeps_promotion_disabled(self) -> None:
policy, controller_receipt, policy_checksum = _valid_controller_receipt()
receipt = verifier.build_verifier_receipt(
policy=policy,
policy_checksum=policy_checksum,
controller=controller_receipt,
controller_sha256="c" * 64,
artifact_controller_sha256="a" * 64,
artifact_verifier_sha256="b" * 64,
observation={
"protocol_version": "2025-06-18",
"server_name": "Playwright",
"server_version": "1.62.0-alpha-1783623505000",
"tool_count": 24,
"tool_name_set_sha256": "d" * 64,
"tool_schema_manifest_sha256": "e" * 64,
"process_exit_code": 0,
"shutdown_mode": "stdin_eof_clean_exit",
"ephemeral_container_removed": True,
},
package_file_count=173,
package_unpacked_bytes=17_000_000,
)
self.assertEqual(receipt["status"], "completed_protocol_schema_replay_verified")
self.assertEqual(
receipt["independent_post_verifier"]["implementation"],
"standalone_no_controller_import",
)
for field in (
"browser_started",
"tool_call_performed",
"external_rag_write_performed",
"production_write_performed",
"promotion_allowed",
):
self.assertIs(receipt[field], False)
def test_failure_receipt_has_rollback_terminal(self) -> None:
policy = _policy_payload()
receipt = verifier._failure_receipt(policy, {}, "fixture_failure")
self.assertEqual(receipt["status"], "blocked_with_safe_next_action")
self.assertEqual(
receipt["rollback_terminal"]["candidate_registration_state"], "disabled"
)
self.assertIs(receipt["promotion_allowed"], False)
if __name__ == "__main__":
unittest.main()

View File

@@ -0,0 +1,898 @@
#!/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