"""Fail-closed verification of immutable deployment artifacts.""" from __future__ import annotations import hashlib from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[1] def _sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as stream: for block in iter(lambda: stream.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def verify_artifacts(manifest: str | Path | None = None) -> dict[str, str]: path = Path(manifest) if manifest is not None else PROJECT_ROOT / "ARTIFACTS.sha256" if not path.is_file(): raise FileNotFoundError(f"artifact manifest is missing: {path}") verified: dict[str, str] = {} for line_number, raw in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): line = raw.strip() if not line or line.startswith("#"): continue parts = line.split(maxsplit=1) if len(parts) != 2 or len(parts[0]) != 64: raise RuntimeError(f"invalid artifact manifest line {line_number}: {raw!r}") expected, relative = parts[0].lower(), parts[1].lstrip(" *") artifact = (PROJECT_ROOT / relative).resolve() if PROJECT_ROOT not in artifact.parents: raise RuntimeError(f"manifest path escapes deployment root: {relative}") if not artifact.is_file(): raise FileNotFoundError(f"manifest artifact is missing: {artifact}") actual = _sha256(artifact) if actual != expected: raise RuntimeError( f"artifact hash mismatch for {relative}: expected {expected}, got {actual}" ) verified[relative] = actual if not verified: raise RuntimeError("artifact manifest contains no files") return verified __all__ = ["verify_artifacts"]