Files
awoooi/scripts/security/external_mcp_artifact_controller.py

1331 lines
49 KiB
Python

#!/usr/bin/env python3
"""Verify and mirror an exact external MCP artifact bundle.
The controller treats marketplace pages as discovery-only input. Executable
artifacts are accepted only from the exact allowlisted registry URLs committed
in policy, after digest, npm registry signature, SLSA subject, dependency,
license, archive-safety, and OSV checks pass. The mirror target is a scratch OCI
bundle in the internal Harbor registry; this controller never starts the MCP
server, a browser, a replay, or a production canary.
"""
from __future__ import annotations
import argparse
import base64
import binascii
import hashlib
import io
import json
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path, PurePosixPath
from typing import Any, Protocol
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlparse
from urllib.request import HTTPRedirectHandler, Request, build_opener
POLICY_SCHEMA_VERSION = "awoooi_external_mcp_artifact_policy_v1"
RECEIPT_SCHEMA_VERSION = "awoooi_external_mcp_artifact_receipt_v1"
SBOM_SPEC_VERSION = "1.5"
SLSA_PREDICATE_TYPE = "https://slsa.dev/provenance/v1"
IN_TOTO_STATEMENT_TYPE = "https://in-toto.io/Statement/v1"
OSV_QUERY_URL = "https://api.osv.dev/v1/query"
OSV_HOST = "api.osv.dev"
TRACE_PATTERN = re.compile(r"^[A-Za-z0-9._:-]{3,160}$")
SHA1_PATTERN = re.compile(r"^[0-9a-f]{40}$")
SHA256_PATTERN = re.compile(r"^sha256:[0-9a-f]{64}$")
SHA512_HEX_PATTERN = re.compile(r"^[0-9a-f]{128}$")
VERSION_PATTERN = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$")
VULNERABILITY_ID_PATTERN = re.compile(r"^[A-Za-z0-9._:+-]{1,160}$")
REQUIRED_PACKAGE_NAMES = {
"@playwright/mcp",
"playwright",
"playwright-core",
}
class ControllerError(RuntimeError):
"""Fail-closed error containing only a public-safe error class."""
class HTTPClient(Protocol):
def get_bytes(self, url: str, *, maximum_bytes: int) -> bytes: ...
def post_json(
self,
url: str,
payload: dict[str, Any],
*,
maximum_bytes: int,
) -> bytes: ...
class _NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001
return None
class BoundedHTTPClient:
"""HTTPS client with redirects disabled and bounded response reads."""
def __init__(self, allowed_hosts: frozenset[str], timeout_seconds: int = 20):
self._allowed_hosts = allowed_hosts
self._timeout_seconds = timeout_seconds
self._opener = build_opener(_NoRedirect())
def get_bytes(self, url: str, *, maximum_bytes: int) -> bytes:
self._validate_url(url)
request = Request(
url,
method="GET",
headers={
"Accept": "application/json, application/octet-stream",
"User-Agent": "awoooi-external-mcp-artifact-controller/1",
},
)
return self._read(request, maximum_bytes)
def post_json(
self,
url: str,
payload: dict[str, Any],
*,
maximum_bytes: int,
) -> bytes:
self._validate_url(url)
request = Request(
url,
method="POST",
data=_canonical_json_bytes(payload),
headers={
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "awoooi-external-mcp-artifact-controller/1",
},
)
return self._read(request, maximum_bytes)
def _validate_url(self, url: str) -> None:
parsed = urlparse(url)
try:
port = parsed.port
except ValueError as exc:
raise ControllerError("source_url_invalid") from exc
if (
parsed.scheme != "https"
or (parsed.hostname or "").lower() not in self._allowed_hosts
or parsed.username is not None
or parsed.password is not None
or port not in {None, 443}
or parsed.query
or parsed.fragment
):
raise ControllerError("source_url_not_allowlisted")
def _read(self, request: Request, maximum_bytes: int) -> bytes:
try:
with self._opener.open(request, timeout=self._timeout_seconds) as response:
if response.status != 200:
raise ControllerError("source_http_status_invalid")
data = response.read(maximum_bytes + 1)
except HTTPError as exc:
if 300 <= exc.code < 400:
raise ControllerError("source_redirect_rejected") from exc
raise ControllerError(f"source_http_{exc.code}") from exc
except (URLError, TimeoutError, OSError) as exc:
raise ControllerError("source_unavailable") from exc
if len(data) > maximum_bytes:
raise ControllerError("source_payload_too_large")
return data
@dataclass(frozen=True)
class PackagePolicy:
name: str
version: str
metadata_url: str
tarball_url: str
integrity: str
sha512_hex: str
shasum_sha1: str
signature: str
attestations_url: str
slsa_subject_name: str
slsa_subject_sha512_hex: str
license: str
dependencies: dict[str, str]
optional_dependencies: dict[str, str]
@property
def filename(self) -> str:
safe_name = self.name.replace("@", "").replace("/", "-")
return f"{safe_name}-{self.version}.tgz"
@property
def bom_ref(self) -> str:
return f"pkg:npm/{quote(self.name, safe='/')}@{self.version}"
@dataclass(frozen=True)
class Policy:
work_item_id: str
candidate_id: str
risk_level: str
allowed_hosts: frozenset[str]
maximum_metadata_bytes: int
maximum_tarball_bytes: int
maximum_unpacked_bytes: int
signature_keyid: str
signature_keytype: str
signature_scheme: str
signature_public_key_der_base64: str
packages: tuple[PackagePolicy, ...]
push_repository: str
runtime_repository: str
mirror_tag: str
allowed_licenses: frozenset[str]
checksum: str
@property
def push_ref(self) -> str:
return f"{self.push_repository}:{self.mirror_tag}"
@dataclass(frozen=True)
class VerifiedPackage:
policy: PackagePolicy
tarball: bytes
attestation: bytes
metadata_sha256: str
tarball_sha512_hex: str
tarball_sha1: str
attestation_sha256: str
unpacked_bytes: int
archive_file_count: int
license_file_count: int
osv_response_sha256: str
vulnerability_ids: tuple[str, ...]
def _canonical_json_bytes(payload: Any) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def _sha256_hex(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _require_text(value: Any, label: str) -> str:
if not isinstance(value, str) or not value.strip():
raise ControllerError(f"invalid_{label}")
return value.strip()
def _require_positive_int(value: Any, label: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
raise ControllerError(f"invalid_{label}")
return value
def _parse_json(data: bytes, error_class: str) -> dict[str, Any]:
try:
payload = json.loads(data.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ControllerError(error_class) from exc
if not isinstance(payload, dict):
raise ControllerError(error_class)
return payload
def _parse_sha512_integrity(value: Any, label: str) -> tuple[str, str]:
integrity = _require_text(value, label)
if not integrity.startswith("sha512-"):
raise ControllerError(f"invalid_{label}")
try:
digest = base64.b64decode(integrity.removeprefix("sha512-"), validate=True)
except (ValueError, binascii.Error) as exc:
raise ControllerError(f"invalid_{label}") from exc
if len(digest) != 64:
raise ControllerError(f"invalid_{label}")
return integrity, digest.hex()
def _validate_exact_version(value: Any, label: str) -> str:
version = _require_text(value, label)
if (
not VERSION_PATTERN.fullmatch(version)
or "latest" in version.lower()
or version.startswith(("^", "~", ">", "<", "*"))
):
raise ControllerError(f"invalid_{label}")
return version
def _validate_https_url(url: str, allowed_hosts: frozenset[str], label: str) -> None:
parsed = urlparse(url)
try:
port = parsed.port
except ValueError as exc:
raise ControllerError(f"invalid_{label}") from exc
if (
parsed.scheme != "https"
or (parsed.hostname or "").lower() not in allowed_hosts
or parsed.username is not None
or parsed.password is not None
or port not in {None, 443}
or parsed.query
or parsed.fragment
):
raise ControllerError(f"invalid_{label}")
def load_policy(path: Path) -> Policy:
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise ControllerError("policy_unreadable") from exc
if not isinstance(payload, dict) or payload.get("schema_version") != POLICY_SCHEMA_VERSION:
raise ControllerError("policy_schema_invalid")
work_item_id = _require_text(payload.get("work_item_id"), "work_item_id")
candidate_id = _require_text(payload.get("candidate_id"), "candidate_id")
if candidate_id != "external.playwright-verifier":
raise ControllerError("candidate_id_not_allowlisted")
if payload.get("risk_level") != "high":
raise ControllerError("risk_level_invalid")
catalog = payload.get("catalog_source")
if not isinstance(catalog, dict):
raise ControllerError("catalog_source_invalid")
if (
catalog.get("kind") != "discovery_only"
or catalog.get("executable_supply_chain_authority") is not False
):
raise ControllerError("catalog_must_be_discovery_only")
registry = payload.get("registry_contract")
if not isinstance(registry, dict):
raise ControllerError("registry_contract_invalid")
allowed_hosts = registry.get("allowed_hosts")
if allowed_hosts != ["registry.npmjs.org"]:
raise ControllerError("registry_hosts_invalid")
host_set = frozenset(allowed_hosts)
if registry.get("redirects_allowed") is not False:
raise ControllerError("registry_redirect_policy_invalid")
registry_origin = _require_text(registry.get("registry_origin"), "registry_origin")
keys_url = _require_text(registry.get("keys_url"), "keys_url")
_validate_https_url(registry_origin, host_set, "registry_origin")
_validate_https_url(keys_url, host_set, "keys_url")
if urlparse(registry_origin).path not in {"", "/"}:
raise ControllerError("registry_origin_invalid")
if urlparse(keys_url).path != "/-/npm/v1/keys":
raise ControllerError("keys_url_invalid")
signature_key = registry.get("signature_key")
if not isinstance(signature_key, dict):
raise ControllerError("signature_key_invalid")
signature_keyid = _require_text(signature_key.get("keyid"), "signature_keyid")
signature_keytype = _require_text(
signature_key.get("keytype"), "signature_keytype"
)
signature_scheme = _require_text(
signature_key.get("scheme"), "signature_scheme"
)
if signature_keytype != "ecdsa-sha2-nistp256" or signature_scheme != signature_keytype:
raise ControllerError("signature_scheme_not_allowlisted")
public_key = _require_text(
signature_key.get("public_key_der_base64"), "signature_public_key"
)
try:
decoded_key = base64.b64decode(public_key, validate=True)
except (ValueError, binascii.Error) as exc:
raise ControllerError("signature_public_key_invalid") from exc
if len(decoded_key) < 80:
raise ControllerError("signature_public_key_invalid")
rows = payload.get("packages")
if not isinstance(rows, list) or len(rows) != len(REQUIRED_PACKAGE_NAMES):
raise ControllerError("package_set_invalid")
packages: list[PackagePolicy] = []
seen: set[str] = set()
for row in rows:
if not isinstance(row, dict):
raise ControllerError("package_policy_invalid")
name = _require_text(row.get("name"), "package_name")
if name in seen:
raise ControllerError("duplicate_package_name")
seen.add(name)
version = _validate_exact_version(row.get("version"), "package_version")
integrity, sha512_hex = _parse_sha512_integrity(
row.get("integrity"), "package_integrity"
)
shasum = _require_text(row.get("shasum_sha1"), "package_shasum")
if not SHA1_PATTERN.fullmatch(shasum):
raise ControllerError("package_shasum_invalid")
slsa_sha512 = _require_text(
row.get("slsa_subject_sha512_hex"), "slsa_subject_sha512"
)
if not SHA512_HEX_PATTERN.fullmatch(slsa_sha512) or slsa_sha512 != sha512_hex:
raise ControllerError("slsa_subject_digest_mismatch")
metadata_url = _require_text(row.get("metadata_url"), "metadata_url")
tarball_url = _require_text(row.get("tarball_url"), "tarball_url")
attestations_url = _require_text(
row.get("attestations_url"), "attestations_url"
)
for url, label in (
(metadata_url, "metadata_url"),
(tarball_url, "tarball_url"),
(attestations_url, "attestations_url"),
):
_validate_https_url(url, host_set, label)
dependencies = _validate_dependency_map(row.get("dependencies", {}))
optional_dependencies = _validate_dependency_map(
row.get("optional_dependencies", {})
)
packages.append(
PackagePolicy(
name=name,
version=version,
metadata_url=metadata_url,
tarball_url=tarball_url,
integrity=integrity,
sha512_hex=sha512_hex,
shasum_sha1=shasum,
signature=_require_text(row.get("signature"), "package_signature"),
attestations_url=attestations_url,
slsa_subject_name=_require_text(
row.get("slsa_subject_name"), "slsa_subject_name"
),
slsa_subject_sha512_hex=slsa_sha512,
license=_require_text(row.get("license"), "package_license"),
dependencies=dependencies,
optional_dependencies=optional_dependencies,
)
)
if seen != REQUIRED_PACKAGE_NAMES:
raise ControllerError("package_set_invalid")
package_versions = {item.name: item.version for item in packages}
for package in packages:
for dependency, version in package.dependencies.items():
if package_versions.get(dependency) != version:
raise ControllerError("dependency_closure_invalid")
mirror = payload.get("internal_mirror")
if not isinstance(mirror, dict):
raise ControllerError("internal_mirror_invalid")
if (
mirror.get("artifact_kind") != "oci_scratch_bundle"
or mirror.get("digest_pin_required") is not True
or mirror.get("sbom_required") is not True
or mirror.get("upstream_signature_required") is not True
or mirror.get("slsa_subject_match_required") is not True
or mirror.get("vulnerability_scan_required") is not True
):
raise ControllerError("internal_mirror_gate_invalid")
push_repository = _require_text(
mirror.get("push_repository"), "push_repository"
)
runtime_repository = _require_text(
mirror.get("runtime_repository"), "runtime_repository"
)
if push_repository != "registry.wooo.work/awoooi/mcp-artifacts/playwright-mcp":
raise ControllerError("push_repository_not_internal")
if runtime_repository != "192.168.0.110:5000/awoooi/mcp-artifacts/playwright-mcp":
raise ControllerError("runtime_repository_not_internal")
mirror_tag = _require_text(mirror.get("tag"), "mirror_tag")
if mirror_tag != "0.0.78-bundle-v1" or "latest" in mirror_tag.lower():
raise ControllerError("mirror_tag_invalid")
allowed_licenses = mirror.get("allowed_licenses")
if allowed_licenses != ["Apache-2.0"]:
raise ControllerError("allowed_licenses_invalid")
for package in packages:
if package.license not in allowed_licenses:
raise ControllerError("package_license_not_allowed")
replay = payload.get("replay_contract")
promotion = payload.get("promotion_contract")
if not isinstance(replay, dict) or not isinstance(promotion, dict):
raise ControllerError("promotion_contract_invalid")
required_false = (
"authenticated_session_allowed",
"browser_profile_persistence_allowed",
"file_upload_allowed",
"download_allowed",
"pdf_write_allowed",
"browser_install_allowed",
"arbitrary_navigation_allowed",
"production_form_submit_allowed",
"production_write_allowed",
)
if any(replay.get(field) is not False for field in required_false):
raise ControllerError("replay_boundary_invalid")
if any(
promotion.get(field) is not False
for field in ("deployment_allowed", "shadow_allowed", "canary_allowed")
):
raise ControllerError("promotion_must_remain_disabled")
prohibited = set(payload.get("prohibited") or [])
if not {
"github_api_or_github_actions",
"actions_checkout",
"github_source_archive_or_ghcr",
"npx_y",
"at_latest",
"floating_container_tag",
"secret_read_or_log",
"request_or_response_body_storage",
"external_rag_write",
"production_mutation",
}.issubset(prohibited):
raise ControllerError("prohibited_actions_incomplete")
return Policy(
work_item_id=work_item_id,
candidate_id=candidate_id,
risk_level="high",
allowed_hosts=host_set,
maximum_metadata_bytes=_require_positive_int(
registry.get("maximum_metadata_bytes"), "maximum_metadata_bytes"
),
maximum_tarball_bytes=_require_positive_int(
registry.get("maximum_tarball_bytes"), "maximum_tarball_bytes"
),
maximum_unpacked_bytes=_require_positive_int(
registry.get("maximum_unpacked_bytes"), "maximum_unpacked_bytes"
),
signature_keyid=signature_keyid,
signature_keytype=signature_keytype,
signature_scheme=signature_scheme,
signature_public_key_der_base64=public_key,
packages=tuple(packages),
push_repository=push_repository,
runtime_repository=runtime_repository,
mirror_tag=mirror_tag,
allowed_licenses=frozenset(allowed_licenses),
checksum="sha256:" + _sha256_hex(_canonical_json_bytes(payload)),
)
def _validate_dependency_map(value: Any) -> dict[str, str]:
if not isinstance(value, dict):
raise ControllerError("dependency_map_invalid")
result: dict[str, str] = {}
for name, version in value.items():
dependency = _require_text(name, "dependency_name")
result[dependency] = _validate_exact_version(version, "dependency_version")
return dict(sorted(result.items()))
def _verify_registry_key(policy: Policy, payload: dict[str, Any]) -> None:
rows = payload.get("keys")
if not isinstance(rows, list):
raise ControllerError("registry_keys_invalid")
matches = [
row
for row in rows
if isinstance(row, dict) and row.get("keyid") == policy.signature_keyid
]
if len(matches) != 1:
raise ControllerError("registry_signature_key_missing")
key = matches[0]
if (
key.get("keytype") != policy.signature_keytype
or key.get("scheme") != policy.signature_scheme
or key.get("key") != policy.signature_public_key_der_base64
or key.get("expires") is not None
):
raise ControllerError("registry_signature_key_drift")
def verify_npm_signature(
*,
public_key_der_base64: str,
signature_base64: str,
message: bytes,
) -> None:
try:
key_bytes = base64.b64decode(public_key_der_base64, validate=True)
signature = base64.b64decode(signature_base64, validate=True)
except (ValueError, binascii.Error) as exc:
raise ControllerError("npm_signature_encoding_invalid") from exc
try:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
public_key = serialization.load_der_public_key(key_bytes)
public_key.verify(signature, message, ec.ECDSA(hashes.SHA256()))
return
except ImportError:
pass
except Exception as exc: # cryptography exposes several backend error types
raise ControllerError("npm_signature_verification_failed") from exc
openssl = shutil.which("openssl")
if not openssl:
raise ControllerError("npm_signature_verifier_unavailable")
with tempfile.TemporaryDirectory(prefix="awoooi-npm-signature-") as directory:
root = Path(directory)
der_path = root / "public.der"
pem_path = root / "public.pem"
signature_path = root / "signature.der"
message_path = root / "message.bin"
der_path.write_bytes(key_bytes)
signature_path.write_bytes(signature)
message_path.write_bytes(message)
convert = subprocess.run(
[
openssl,
"pkey",
"-pubin",
"-inform",
"DER",
"-in",
str(der_path),
"-out",
str(pem_path),
],
check=False,
capture_output=True,
text=True,
timeout=10,
)
if convert.returncode != 0:
raise ControllerError("npm_signature_public_key_invalid")
verified = subprocess.run(
[
openssl,
"dgst",
"-sha256",
"-verify",
str(pem_path),
"-signature",
str(signature_path),
str(message_path),
],
check=False,
capture_output=True,
text=True,
timeout=10,
)
if verified.returncode != 0:
raise ControllerError("npm_signature_verification_failed")
def _validate_metadata(
package: PackagePolicy,
metadata: dict[str, Any],
policy: Policy,
) -> None:
if metadata.get("name") != package.name or metadata.get("version") != package.version:
raise ControllerError("registry_package_identity_mismatch")
if metadata.get("license") != package.license:
raise ControllerError("registry_package_license_mismatch")
if _validate_dependency_map(metadata.get("dependencies", {})) != package.dependencies:
raise ControllerError("registry_dependency_drift")
if (
_validate_dependency_map(metadata.get("optionalDependencies", {}))
!= package.optional_dependencies
):
raise ControllerError("registry_optional_dependency_drift")
dist = metadata.get("dist")
if not isinstance(dist, dict):
raise ControllerError("registry_dist_missing")
if (
dist.get("integrity") != package.integrity
or dist.get("shasum") != package.shasum_sha1
or dist.get("tarball") != package.tarball_url
):
raise ControllerError("registry_dist_drift")
signatures = dist.get("signatures")
expected_signature = {
"keyid": policy.signature_keyid,
"sig": package.signature,
}
if not isinstance(signatures, list) or expected_signature not in signatures:
raise ControllerError("registry_package_signature_drift")
attestations = dist.get("attestations")
if not isinstance(attestations, dict):
raise ControllerError("registry_attestations_missing")
if (
attestations.get("url") != package.attestations_url
or not isinstance(attestations.get("provenance"), dict)
or attestations["provenance"].get("predicateType") != SLSA_PREDICATE_TYPE
):
raise ControllerError("registry_attestation_drift")
message = f"{package.name}@{package.version}:{package.integrity}".encode()
verify_npm_signature(
public_key_der_base64=policy.signature_public_key_der_base64,
signature_base64=package.signature,
message=message,
)
def validate_slsa_attestation(
package: PackagePolicy,
payload: dict[str, Any],
) -> None:
rows = payload.get("attestations")
if not isinstance(rows, list):
raise ControllerError("slsa_attestation_invalid")
slsa_rows = [
row
for row in rows
if isinstance(row, dict) and row.get("predicateType") == SLSA_PREDICATE_TYPE
]
if len(slsa_rows) != 1:
raise ControllerError("slsa_attestation_missing")
bundle = slsa_rows[0].get("bundle")
envelope = bundle.get("dsseEnvelope") if isinstance(bundle, dict) else None
encoded = envelope.get("payload") if isinstance(envelope, dict) else None
if not isinstance(encoded, str):
raise ControllerError("slsa_dsse_payload_missing")
try:
statement = json.loads(base64.b64decode(encoded, validate=True))
except (ValueError, binascii.Error, json.JSONDecodeError) as exc:
raise ControllerError("slsa_dsse_payload_invalid") from exc
if not isinstance(statement, dict):
raise ControllerError("slsa_statement_invalid")
if (
statement.get("_type") != IN_TOTO_STATEMENT_TYPE
or statement.get("predicateType") != SLSA_PREDICATE_TYPE
):
raise ControllerError("slsa_statement_type_invalid")
subjects = statement.get("subject")
expected_subject = {
"name": package.slsa_subject_name,
"digest": {"sha512": package.slsa_subject_sha512_hex},
}
if subjects != [expected_subject]:
raise ControllerError("slsa_subject_mismatch")
def inspect_tarball(
package: PackagePolicy,
data: bytes,
*,
maximum_unpacked_bytes: int,
) -> tuple[int, int, int]:
try:
archive = tarfile.open(fileobj=io.BytesIO(data), mode="r:gz")
except (tarfile.TarError, OSError) as exc:
raise ControllerError("package_tarball_invalid") from exc
unpacked_bytes = 0
file_count = 0
license_file_count = 0
seen_files: set[str] = set()
package_json: dict[str, Any] | None = None
with archive:
members = archive.getmembers()
if not members or len(members) > 1024:
raise ControllerError("package_archive_entry_count_invalid")
for member in members:
path = PurePosixPath(member.name)
if (
path.is_absolute()
or ".." in path.parts
or not path.parts
or path.parts[0] != "package"
or member.issym()
or member.islnk()
or member.isdev()
):
raise ControllerError("package_archive_unsafe_entry")
if member.isfile():
normalized_path = str(path)
if member.size < 0 or normalized_path in seen_files:
raise ControllerError("package_archive_duplicate_or_invalid_file")
seen_files.add(normalized_path)
file_count += 1
unpacked_bytes += member.size
if unpacked_bytes > maximum_unpacked_bytes:
raise ControllerError("package_archive_unpacked_size_exceeded")
basename = path.name.lower()
if basename.startswith(("license", "licence", "copying")):
license_file_count += 1
if path == PurePosixPath("package/package.json"):
extracted = archive.extractfile(member)
if extracted is None:
raise ControllerError("package_manifest_unreadable")
package_json = _parse_json(
extracted.read(262_145), "package_manifest_invalid"
)
if package_json is None:
raise ControllerError("package_manifest_missing")
if (
package_json.get("name") != package.name
or package_json.get("version") != package.version
or package_json.get("license") != package.license
):
raise ControllerError("package_manifest_identity_mismatch")
if _validate_dependency_map(package_json.get("dependencies", {})) != package.dependencies:
raise ControllerError("package_manifest_dependency_drift")
if (
_validate_dependency_map(package_json.get("optionalDependencies", {}))
!= package.optional_dependencies
):
raise ControllerError("package_manifest_optional_dependency_drift")
if license_file_count == 0:
raise ControllerError("package_license_file_missing")
return unpacked_bytes, file_count, license_file_count
def _query_osv(
package: PackagePolicy,
http: HTTPClient,
maximum_bytes: int,
) -> tuple[str, tuple[str, ...]]:
data = http.post_json(
OSV_QUERY_URL,
{
"package": {"ecosystem": "npm", "name": package.name},
"version": package.version,
},
maximum_bytes=maximum_bytes,
)
payload = _parse_json(data, "osv_response_invalid")
rows = payload.get("vulns", [])
if not isinstance(rows, list):
raise ControllerError("osv_response_invalid")
vulnerability_ids: list[str] = []
for row in rows:
identifier = row.get("id") if isinstance(row, dict) else None
if not isinstance(identifier, str) or not VULNERABILITY_ID_PATTERN.fullmatch(
identifier
):
raise ControllerError("osv_vulnerability_id_invalid")
vulnerability_ids.append(identifier)
return _sha256_hex(data), tuple(sorted(set(vulnerability_ids)))
def verify_upstream_bundle(policy: Policy, http: HTTPClient) -> tuple[VerifiedPackage, ...]:
keys_bytes = http.get_bytes(
"https://registry.npmjs.org/-/npm/v1/keys",
maximum_bytes=policy.maximum_metadata_bytes,
)
_verify_registry_key(policy, _parse_json(keys_bytes, "registry_keys_invalid"))
verified: list[VerifiedPackage] = []
for package in policy.packages:
metadata_bytes = http.get_bytes(
package.metadata_url,
maximum_bytes=policy.maximum_metadata_bytes,
)
metadata = _parse_json(metadata_bytes, "registry_metadata_invalid")
_validate_metadata(package, metadata, policy)
attestation_bytes = http.get_bytes(
package.attestations_url,
maximum_bytes=policy.maximum_metadata_bytes,
)
validate_slsa_attestation(
package,
_parse_json(attestation_bytes, "slsa_attestation_invalid"),
)
tarball = http.get_bytes(
package.tarball_url,
maximum_bytes=policy.maximum_tarball_bytes,
)
tarball_sha512 = hashlib.sha512(tarball).hexdigest()
tarball_sha1 = hashlib.sha1(tarball, usedforsecurity=False).hexdigest()
if (
tarball_sha512 != package.sha512_hex
or tarball_sha1 != package.shasum_sha1
):
raise ControllerError("package_tarball_digest_mismatch")
unpacked_bytes, archive_file_count, license_file_count = inspect_tarball(
package,
tarball,
maximum_unpacked_bytes=policy.maximum_unpacked_bytes,
)
osv_sha256, vulnerability_ids = _query_osv(
package,
http,
policy.maximum_metadata_bytes,
)
if vulnerability_ids:
raise ControllerError("vulnerability_decision_blocked")
verified.append(
VerifiedPackage(
policy=package,
tarball=tarball,
attestation=attestation_bytes,
metadata_sha256=_sha256_hex(metadata_bytes),
tarball_sha512_hex=tarball_sha512,
tarball_sha1=tarball_sha1,
attestation_sha256=_sha256_hex(attestation_bytes),
unpacked_bytes=unpacked_bytes,
archive_file_count=archive_file_count,
license_file_count=license_file_count,
osv_response_sha256=osv_sha256,
vulnerability_ids=vulnerability_ids,
)
)
return tuple(verified)
def build_cyclonedx_sbom(
policy: Policy,
verified: tuple[VerifiedPackage, ...],
) -> dict[str, Any]:
by_name = {item.policy.name: item for item in verified}
components: list[dict[str, Any]] = []
dependencies: list[dict[str, Any]] = []
for package in policy.packages:
evidence = by_name[package.name]
components.append(
{
"type": "library",
"bom-ref": package.bom_ref,
"group": package.name.split("/", 1)[0].removeprefix("@")
if package.name.startswith("@")
else "",
"name": package.name.split("/", 1)[-1],
"version": package.version,
"purl": package.bom_ref,
"hashes": [
{"alg": "SHA-1", "content": evidence.tarball_sha1},
{"alg": "SHA-512", "content": evidence.tarball_sha512_hex},
],
"licenses": [{"license": {"id": package.license}}],
"externalReferences": [
{"type": "distribution", "url": package.tarball_url},
{"type": "build-meta", "url": package.attestations_url},
],
}
)
dependencies.append(
{
"ref": package.bom_ref,
"dependsOn": [
by_name[name].policy.bom_ref
for name in sorted(package.dependencies)
],
}
)
serial = uuid.uuid5(uuid.NAMESPACE_URL, f"{policy.candidate_id}:{policy.checksum}")
return {
"bomFormat": "CycloneDX",
"specVersion": SBOM_SPEC_VERSION,
"serialNumber": f"urn:uuid:{serial}",
"version": 1,
"metadata": {
"component": {
"type": "application",
"bom-ref": f"awoooi:mcp-artifact:{policy.candidate_id}",
"name": policy.candidate_id,
"version": policy.packages[0].version,
},
"tools": {
"components": [
{
"type": "application",
"name": "awoooi-external-mcp-artifact-controller",
"version": "1",
}
]
},
},
"components": components,
"dependencies": dependencies,
}
def _write_bundle(
directory: Path,
policy: Policy,
verified: tuple[VerifiedPackage, ...],
sbom: dict[str, Any],
) -> tuple[str, str]:
bundle = directory / "bundle"
tarballs = bundle / "tarballs"
attestations = bundle / "attestations"
tarballs.mkdir(parents=True)
attestations.mkdir(parents=True)
package_rows: list[dict[str, Any]] = []
for item in verified:
package = item.policy
tarball_path = tarballs / package.filename
attestation_path = attestations / f"{package.filename}.attestations.json"
tarball_path.write_bytes(item.tarball)
attestation_path.write_bytes(item.attestation)
package_rows.append(
{
"name": package.name,
"version": package.version,
"tarball_path": f"tarballs/{package.filename}",
"tarball_sha512_hex": item.tarball_sha512_hex,
"tarball_sha1": item.tarball_sha1,
"metadata_sha256": item.metadata_sha256,
"attestation_path": (
f"attestations/{package.filename}.attestations.json"
),
"attestation_sha256": item.attestation_sha256,
"npm_registry_signature_verified": True,
"slsa_subject_verified": True,
"archive_safety_verified": True,
"license": package.license,
"license_file_count": item.license_file_count,
"osv_response_sha256": item.osv_response_sha256,
"vulnerability_ids": list(item.vulnerability_ids),
}
)
sbom_bytes = _canonical_json_bytes(sbom) + b"\n"
sbom_path = bundle / "sbom.cdx.json"
sbom_path.write_bytes(sbom_bytes)
manifest = {
"schema_version": "awoooi_external_mcp_artifact_bundle_v1",
"candidate_id": policy.candidate_id,
"work_item_id": policy.work_item_id,
"policy_checksum": policy.checksum,
"package_count": len(package_rows),
"packages": package_rows,
"sbom": {
"path": "sbom.cdx.json",
"format": "CycloneDX",
"spec_version": SBOM_SPEC_VERSION,
"sha256": _sha256_hex(sbom_bytes),
},
"operation_boundaries": {
"mcp_server_started": False,
"browser_started": False,
"external_tool_call_performed": False,
"external_rag_write_performed": False,
"production_write_performed": False,
"runtime_request_or_response_body_stored": False,
"upstream_supply_chain_attestation_bundled": True,
},
}
manifest_bytes = _canonical_json_bytes(manifest) + b"\n"
(bundle / "bundle-manifest.json").write_bytes(manifest_bytes)
dockerfile = (
"FROM scratch\n"
"COPY bundle/ /artifact/\n"
f'LABEL org.opencontainers.image.title="{policy.candidate_id}"\n'
f'LABEL org.opencontainers.image.version="{policy.packages[0].version}"\n'
f'LABEL io.awoooi.policy-checksum="{policy.checksum}"\n'
)
(directory / "Dockerfile").write_text(dockerfile, encoding="utf-8")
return _sha256_hex(manifest_bytes), _sha256_hex(sbom_bytes)
def _run_command(command: list[str], *, cwd: Path, timeout: int) -> str:
try:
completed = subprocess.run(
command,
cwd=cwd,
check=False,
capture_output=True,
text=True,
timeout=timeout,
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise ControllerError("bounded_execution_command_failed") from exc
if completed.returncode != 0:
raise ControllerError("bounded_execution_command_failed")
return completed.stdout + completed.stderr
def mirror_bundle(
policy: Policy,
verified: tuple[VerifiedPackage, ...],
sbom: dict[str, Any],
) -> tuple[str, str, str, str]:
docker = shutil.which("docker")
if not docker:
raise ControllerError("docker_cli_unavailable")
with tempfile.TemporaryDirectory(prefix="awoooi-playwright-mcp-bundle-") as temp:
context = Path(temp)
manifest_sha256, sbom_sha256 = _write_bundle(
context,
policy,
verified,
sbom,
)
_run_command(
[
docker,
"build",
"--network=none",
"--pull=false",
"--provenance=false",
"--sbom=false",
"--file",
str(context / "Dockerfile"),
"--tag",
policy.push_ref,
".",
],
cwd=context,
timeout=300,
)
push_output = _run_command(
[docker, "push", policy.push_ref],
cwd=context,
timeout=300,
)
matches = re.findall(r"digest:\s+(sha256:[0-9a-f]{64})", push_output)
if not matches:
inspected = _run_command(
[
docker,
"image",
"inspect",
"--format",
"{{json .RepoDigests}}",
policy.push_ref,
],
cwd=context,
timeout=30,
)
try:
repo_digests = json.loads(inspected.strip())
except json.JSONDecodeError as exc:
raise ControllerError("internal_mirror_digest_missing") from exc
matches = [
value.rsplit("@", 1)[1]
for value in repo_digests
if isinstance(value, str)
and value.startswith(policy.push_repository + "@sha256:")
]
digest = matches[-1] if matches else ""
if not SHA256_PATTERN.fullmatch(digest):
raise ControllerError("internal_mirror_digest_invalid")
digest_ref = f"{policy.push_repository}@{digest}"
_run_command(
[docker, "buildx", "imagetools", "inspect", digest_ref],
cwd=context,
timeout=60,
)
runtime_ref = f"{policy.runtime_repository}@{digest}"
return digest_ref, runtime_ref, manifest_sha256, sbom_sha256
def build_receipt(
*,
policy: Policy,
verified: tuple[VerifiedPackage, ...],
sbom_sha256: str,
bundle_manifest_sha256: str,
run_id: str,
trace_id: str,
mode: str,
internal_mirror_ref: str | None,
runtime_mirror_ref: str | None,
) -> dict[str, Any]:
observed_at = datetime.now(timezone.utc).isoformat()
apply_performed = internal_mirror_ref is not None
terminal = (
"completed_internal_mirror_pending_independent_verifier"
if apply_performed
else "completed_check_mode_pending_independent_verifier"
)
package_rows = [
{
"name": item.policy.name,
"version": item.policy.version,
"metadata_sha256": item.metadata_sha256,
"tarball_sha512_hex": item.tarball_sha512_hex,
"tarball_sha1": item.tarball_sha1,
"attestation_sha256": item.attestation_sha256,
"npm_registry_signature_verified": True,
"slsa_subject_verified": True,
"archive_safety_verified": True,
"license_decision": "passed",
"vulnerability_decision": "passed_no_known_osv_records",
"vulnerability_ids": list(item.vulnerability_ids),
"unpacked_bytes": item.unpacked_bytes,
"archive_file_count": item.archive_file_count,
}
for item in verified
]
return {
"schema_version": RECEIPT_SCHEMA_VERSION,
"run_id": run_id,
"trace_id": trace_id,
"work_item_id": policy.work_item_id,
"candidate_id": policy.candidate_id,
"risk_level": policy.risk_level,
"mode": mode,
"status": terminal,
"observed_at": observed_at,
"policy_checksum": policy.checksum,
"package_count": len(package_rows),
"packages": package_rows,
"internal_mirror_ref": internal_mirror_ref,
"runtime_mirror_ref": runtime_mirror_ref,
"bundle_manifest_sha256": bundle_manifest_sha256,
"cyclonedx_sbom_sha256": sbom_sha256,
"promotion_allowed": False,
"replay_allowed": False,
"shadow_allowed": False,
"canary_allowed": False,
"stage_receipts": {
"sensor_source_receipt": {
"registry": "registry.npmjs.org",
"osv": "api.osv.dev",
"redirects_followed": 0,
"runtime_request_or_response_body_stored": False,
"upstream_supply_chain_attestation_bundled": True,
},
"normalized_asset_identity": {
"candidate_id": policy.candidate_id,
"exact_root_version": policy.packages[0].version,
"package_count": len(package_rows),
},
"source_of_truth_diff": {
"change_state": "baseline_created",
"policy_checksum": policy.checksum,
},
"decision": {
"candidate_action": (
"run_independent_mirror_verifier_then_implement_replay_adapter"
if apply_performed
else "run_independent_check_verifier_then_mirror_exact_bundle"
),
"runtime_promotion_allowed": False,
},
"risk_policy": {
"risk_level": "high",
"exact_versions": True,
"immutable_upstream_digests": True,
"npm_registry_signatures_verified": True,
"slsa_subjects_verified": True,
"license_decision": "passed",
"vulnerability_decision": "passed_no_known_osv_records",
},
"check_mode": {
"status": "passed",
"mcp_process_started": False,
"browser_started": False,
"production_write_performed": False,
},
"bounded_execution": {
"status": "internal_oci_bundle_pushed" if apply_performed else "not_executed",
"internal_registry_write_performed": apply_performed,
"external_runtime_mutation_performed": False,
"production_route_changed": False,
},
"independent_post_verifier": {
"status": "pending_external_verifier",
"controller_registry_digest_check": apply_performed,
"internal_digest_ref_verified": False,
"bundle_manifest_sha256": bundle_manifest_sha256,
"cyclonedx_sbom_sha256": sbom_sha256,
"package_digest_count": len(package_rows),
},
"rollback_or_no_write_terminal": {
"status": (
"pending_external_verifier"
if apply_performed
else "no_write_pending_external_verifier"
),
"rollback_action": "disable_candidate_and_retain_immutable_bundle",
"external_artifact_delete_allowed": False,
"production_route_change_allowed": False,
},
"learning_writeback": {
"status": "receipt_ready_for_committed_writeback",
"rag_write_performed": False,
"km_write_performed": False,
},
},
}
def _write_receipt(path: Path, receipt: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(_canonical_json_bytes(receipt) + b"\n")
def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--policy", type=Path, required=True)
parser.add_argument("--run-id", required=True)
parser.add_argument("--trace-id", required=True)
parser.add_argument("--receipt", type=Path)
parser.add_argument("command", choices=("check", "mirror"))
parser.add_argument("--apply", action="store_true")
return parser
def main(argv: list[str] | None = None) -> int:
args = _build_parser().parse_args(argv)
try:
normalized_run_id = str(uuid.UUID(args.run_id))
except (ValueError, AttributeError) as exc:
raise ControllerError("run_id_invalid") from exc
if normalized_run_id != args.run_id:
raise ControllerError("run_id_not_canonical")
if not TRACE_PATTERN.fullmatch(args.trace_id):
raise ControllerError("trace_id_invalid")
if args.trace_id != f"mcp-artifact-{normalized_run_id}":
raise ControllerError("run_trace_identity_mismatch")
if args.command == "check" and args.apply:
raise ControllerError("check_mode_cannot_apply")
if args.command == "mirror" and args.apply and args.receipt is None:
raise ControllerError("apply_receipt_path_required")
policy = load_policy(args.policy)
http = BoundedHTTPClient(policy.allowed_hosts | frozenset({OSV_HOST}))
verified = verify_upstream_bundle(policy, http)
sbom = build_cyclonedx_sbom(policy, verified)
with tempfile.TemporaryDirectory(prefix="awoooi-playwright-mcp-check-") as temp:
manifest_sha256, sbom_sha256 = _write_bundle(
Path(temp),
policy,
verified,
sbom,
)
internal_ref = None
runtime_ref = None
if args.command == "mirror" and args.apply:
internal_ref, runtime_ref, manifest_sha256, sbom_sha256 = mirror_bundle(
policy,
verified,
sbom,
)
receipt = build_receipt(
policy=policy,
verified=verified,
sbom_sha256=sbom_sha256,
bundle_manifest_sha256=manifest_sha256,
run_id=normalized_run_id,
trace_id=args.trace_id,
mode=("apply" if args.command == "mirror" and args.apply else "check"),
internal_mirror_ref=internal_ref,
runtime_mirror_ref=runtime_ref,
)
if args.receipt is not None:
_write_receipt(args.receipt, receipt)
print(json.dumps(receipt, ensure_ascii=False, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except ControllerError as exc:
print(
json.dumps(
{
"schema_version": RECEIPT_SCHEMA_VERSION,
"status": "blocked_with_safe_next_action",
"error_class": str(exc),
},
sort_keys=True,
),
file=sys.stderr,
)
raise SystemExit(1) from None