Files
deusdata--codebase-memory-mcp/scripts/ci/verify-release-selection.py
Martin Vogel 7b4533b67d fix(ci): finish the three-candidate wiring and restore full-surface VT scanning
Two things, both found by the dry-run and by sweeping what it exposed.

1. The three-candidate change was incomplete. The dry-run failed at
   stage-release-candidates.py, which carries its OWN copy of the transform
   validation that the previous commit only fixed in the selector:

     stage-release-candidates: candidate linux-amd64/debug-stripped has
     invalid transform: 'strip-debug'

   Sweeping for that assumption found it in six places, not two:
   stage-release-candidates.py, select-release-candidates.py,
   verify-release-selection.py, append-vt-notes.sh and two contract-test
   fixtures — as hardcoded 16s, `len(TARGETS) * 2`, two-entry VARIANTS tuples
   and two-key truth tables. All are now derived from len(VARIANTS).

   Field prefixes needed care: the variant NAME keeps its hyphen because it is
   the on-disk directory, while the evidence columns use underscores, so
   `debug-stripped` reads `debug_stripped_sha256`. Every lookup now goes through
   an explicit FIELD_KEY map instead of interpolating the variant directly.

   The selection contract test now covers the truth table EXHAUSTIVELY: three
   variants x two tolerated classifications is exactly eight combinations, and
   there are exactly eight targets, so every case is exercised once.

2. Full-surface VirusTotal scanning is restored. This PR had moved scanning
   upstream to the candidates and deleted the post-package pass, which silently
   narrowed coverage from everything we ship to executables only. The 42 runtime
   files across the 14 containers — install.sh, install.ps1, LICENSE,
   THIRD_PARTY_NOTICES.md, the MCPB manifest.json and the unpacked UI assets —
   were still extracted, structurally verified and strings-audited, but no
   longer scanned at all. install.sh and install.ps1 are the highest-consequence
   non-executable bytes we publish; users pipe them straight into a shell.

   The verify job scans every extracted object again, under the same policy.
   Re-submitting the selected executables alongside them is close to free
   because VirusTotal is content-addressed and answers for identical bytes from
   its own record — the same property that made the analysis-id equality check
   untenable two commits ago.

   The gate-chain contract asserted the opposite ("duplicate post-package
   VirusTotal path remains"). That assertion is inverted: the pass is required,
   and it is not a duplicate, since it covers a strictly larger set. README and
   SECURITY.md updated from "archive containers are checksummed rather than
   redundantly rescanned" to state the full covered surface.

All five release/VT contract tests pass.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-14 00:21:31 +02:00

436 lines
19 KiB
Python
Executable File

#!/usr/bin/env python3
"""Reconcile every canonical public release container to selected bytes."""
from __future__ import annotations
import argparse
import csv
import hashlib
import os
import pathlib
import re
import stat
import sys
import tarfile
import zipfile
from collections.abc import Sequence
from typing import BinaryIO, Optional
class ContractError(Exception):
"""The release-container namespace or selected-byte binding is invalid."""
TARGETS = (
"linux-amd64",
"linux-arm64",
"linux-amd64-portable",
"linux-arm64-portable",
"darwin-amd64",
"darwin-arm64",
"windows-amd64",
"windows-arm64",
)
VARIANTS = ("unstripped", "debug-stripped", "stripped")
FIELD_KEY = {variant: variant.replace("-", "_") for variant in VARIANTS}
SELECTION_FIELDS = (
"target",
"selected_variant",
"selected_path",
"selected_sha256",
"selected_size",
"decision",
"unstripped_sha256",
"unstripped_scan_path",
"unstripped_classification",
"unstripped_analysis_id",
"unstripped_virustotal_url",
"debug_stripped_sha256",
"debug_stripped_scan_path",
"debug_stripped_classification",
"debug_stripped_analysis_id",
"debug_stripped_virustotal_url",
"stripped_sha256",
"stripped_scan_path",
"stripped_classification",
"stripped_analysis_id",
"stripped_virustotal_url",
)
SHA256_RE = re.compile(r"\A[0-9a-f]{64}\Z")
ANALYSIS_ID_RE = re.compile(r"\A[A-Za-z0-9_+/-]+={0,2}\Z")
MAX_MANIFEST_BYTES = 16 * 1024 * 1024
MAX_ARCHIVE_BYTES = 1024 * 1024 * 1024
MAX_MEMBER_BYTES = 512 * 1024 * 1024
MAX_TOTAL_MEMBER_BYTES = 2 * 1024 * 1024 * 1024
MAX_MEMBERS = 128
def archive_specs() -> dict[str, tuple[str, str]]:
specs: dict[str, tuple[str, str]] = {}
for target in TARGETS:
binary = "codebase-memory-mcp.exe" if target.startswith("windows-") else "codebase-memory-mcp"
suffix = ".zip" if target.startswith("windows-") else ".tar.gz"
specs[f"codebase-memory-mcp-{target}{suffix}"] = (target, binary)
if (
target.startswith(("darwin-", "windows-"))
or target.endswith("-portable")
):
specs[f"codebase-memory-mcp-{target}.mcpb"] = (
target,
f"server/{binary}",
)
return specs
ARCHIVES = archive_specs()
def parse_arguments(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Bind all fourteen canonical release containers to release-selection.tsv."
)
parser.add_argument("--selection", required=True, type=pathlib.Path)
parser.add_argument("--archive-dir", required=True, type=pathlib.Path)
parser.add_argument(
"--require-policy",
choices=("virustotal-v2", "unscanned-dry-run"),
help="reject a selection produced under any other policy",
)
return parser.parse_args(argv[1:])
def regular_status(path: pathlib.Path, *, ceiling: int, label: str) -> os.stat_result:
try:
status = path.lstat()
except FileNotFoundError as error:
raise ContractError(f"missing {label}: {path}") from error
if not stat.S_ISREG(status.st_mode):
raise ContractError(f"{label} is not a regular non-symlink file: {path}")
if status.st_size > ceiling:
raise ContractError(f"{label} exceeds the {ceiling}-byte ceiling: {path}")
return status
def load_selection(
path: pathlib.Path, *, require_policy: Optional[str] = None
) -> dict[str, dict[str, str]]:
regular_status(path, ceiling=MAX_MANIFEST_BYTES, label="release selection")
try:
text = path.read_text(encoding="utf-8")
except UnicodeError as error:
raise ContractError("release selection is not UTF-8") from error
if "\x00" in text or "\r" in text:
raise ContractError("release selection contains forbidden control bytes")
lines = text.splitlines()
if not lines or lines[0] != "# cbm-release-selection-v1":
raise ContractError("release selection marker is missing")
metadata: dict[str, str] = {}
cursor = 1
while cursor < len(lines) and lines[cursor].startswith("# "):
raw = lines[cursor][2:]
key, separator, value = raw.partition("=")
if not separator or not key or not value or key in metadata:
raise ContractError(f"malformed or duplicate release-selection metadata: {raw!r}")
metadata[key] = value
cursor += 1
if set(metadata) != {"policy", "targets", "candidates"}:
raise ContractError("release selection metadata fields are incomplete or unexpected")
if metadata["policy"] not in {"virustotal-v2", "unscanned-dry-run"}:
raise ContractError("release selection has an invalid policy")
if require_policy is not None and metadata["policy"] != require_policy:
raise ContractError(
f"release selection policy is {metadata['policy']!r}; required {require_policy!r}"
)
if metadata["targets"] != str(len(TARGETS)) or metadata["candidates"] != str(
len(TARGETS) * len(VARIANTS)
):
raise ContractError("release selection metadata does not bind the canonical matrix")
reader = csv.DictReader(lines[cursor:], delimiter="\t")
if tuple(reader.fieldnames or ()) != SELECTION_FIELDS:
raise ContractError("release selection header is malformed or unexpected")
rows = list(reader)
if any(
None in row
or any(row.get(field) is None for field in SELECTION_FIELDS)
or any(
any(ord(character) < 32 for character in value)
for value in row.values()
if value
)
for row in rows
):
raise ContractError("release selection has missing or surplus cells")
if [row["target"] for row in rows] != list(TARGETS):
raise ContractError("release selection is not the exact canonical target matrix")
selections: dict[str, dict[str, str]] = {}
analysis_ids: set[str] = set()
for row in rows:
target = row["target"]
binary = "codebase-memory-mcp.exe" if target.startswith("windows-") else "codebase-memory-mcp"
classifications = {
variant: row[f"{FIELD_KEY[variant]}_classification"] for variant in VARIANTS
}
if metadata["policy"] == "virustotal-v2":
if any(
classification not in {"clean", "microsoft-ml"}
for classification in classifications.values()
):
raise ContractError(f"release selection contains a hard classification: {target}")
for variant in VARIANTS:
sha256 = row[f"{FIELD_KEY[variant]}_sha256"]
analysis_id = row[f"{FIELD_KEY[variant]}_analysis_id"]
expected_url = f"https://www.virustotal.com/gui/file/{sha256}/detection"
if ANALYSIS_ID_RE.fullmatch(analysis_id) is None:
raise ContractError(f"release selection lacks VT analysis evidence: {target}")
if analysis_id in analysis_ids:
raise ContractError("release selection reuses one VT analysis id")
analysis_ids.add(analysis_id)
if row[f"{FIELD_KEY[variant]}_virustotal_url"] != expected_url:
raise ContractError(f"release selection has unbound VT URL: {target}")
# Re-derived here independently of the selector: smallest artifact
# first, first CLEAN one wins, and only if every candidate drew the
# tolerated verdict do we ship the smallest flagged one.
order = ("stripped", "debug-stripped", "unstripped")
clean = [v for v in order if classifications[v] == "clean"]
if clean:
expected_variant = clean[0]
if expected_variant == "stripped":
expected_decision = "stripped-preferred"
else:
others = "-".join(
f"{v}:{classifications[v]}" for v in order if v != expected_variant
)
expected_decision = f"{expected_variant}-clean-after-{others}"
else:
expected_variant = "stripped"
expected_decision = "stripped-all-candidates-microsoft-ml"
else:
if any(
classification != "unscanned-dry-run"
for classification in classifications.values()
):
raise ContractError(f"dry-run selection disguises unscanned evidence: {target}")
if any(
row[f"{FIELD_KEY[variant]}_{suffix}"]
for variant in VARIANTS
for suffix in ("analysis_id", "virustotal_url")
):
raise ContractError(f"dry-run selection contains invented VT evidence: {target}")
expected_variant = "stripped"
expected_decision = "stripped-unscanned-dry-run"
if (
row["selected_variant"] != expected_variant
or row["decision"] != expected_decision
):
raise ContractError(f"release selection contradicts the policy truth table: {target}")
selected_variant = expected_variant
if row["selected_path"] != f"selected/{target}/{binary}":
raise ContractError(f"release selection has unsafe selected path: {target}")
if (
SHA256_RE.fullmatch(row["selected_sha256"]) is None
or row["selected_sha256"] != row[f"{FIELD_KEY[selected_variant]}_sha256"]
or not row["selected_size"].isdigit()
or not 0 < int(row["selected_size"]) <= MAX_MEMBER_BYTES
):
raise ContractError(f"release selection has invalid selected content: {target}")
for variant in VARIANTS:
sha256 = row[f"{FIELD_KEY[variant]}_sha256"]
scan_path = pathlib.PurePosixPath(row[f"{FIELD_KEY[variant]}_scan_path"])
if (
SHA256_RE.fullmatch(sha256) is None
or scan_path.as_posix() != f"objects/{sha256}"
):
raise ContractError(f"release selection has invalid sibling binding: {target}")
if row["unstripped_sha256"] == row["stripped_sha256"]:
raise ContractError(f"release selection siblings are not byte-distinct: {target}")
selections[target] = row
return selections
def safe_member_name(name: str) -> bool:
if not name or "\\" in name or "\x00" in name or name.startswith("/"):
return False
pure = pathlib.PurePosixPath(name)
return (
not pure.is_absolute()
and pure.as_posix() == name
and all(part not in ("", ".", "..") for part in pure.parts)
)
def hash_stream(stream: BinaryIO, *, declared_size: int, label: str) -> tuple[str, int]:
if declared_size < 0 or declared_size > MAX_MEMBER_BYTES:
raise ContractError(f"archive member has an invalid size: {label}")
digest = hashlib.sha256()
total = 0
while True:
chunk = stream.read(min(1024 * 1024, declared_size - total + 1))
if not chunk:
break
total += len(chunk)
if total > declared_size or total > MAX_MEMBER_BYTES:
raise ContractError(f"archive member exceeded its declared size: {label}")
digest.update(chunk)
if total != declared_size:
raise ContractError(f"archive member size does not match metadata: {label}")
return digest.hexdigest(), total
def verify_tar(
path: pathlib.Path, *, required_member: str, expected_sha256: str,
expected_size: int, require_executable_mode: bool
) -> None:
matches = 0
try:
with tarfile.open(path, mode="r:gz") as archive:
members = archive.getmembers()
if not members or len(members) > MAX_MEMBERS:
raise ContractError(f"tar member count is invalid: {path.name}")
if sum(member.size for member in members) > MAX_TOTAL_MEMBER_BYTES:
raise ContractError(f"tar exceeds total member-size ceiling: {path.name}")
seen: set[str] = set()
for member in members:
if not safe_member_name(member.name) or member.name in seen:
raise ContractError(f"tar has unsafe or duplicate member: {path.name}:{member.name}")
seen.add(member.name)
if member.issym() or member.islnk() or member.isdev() or member.isfifo():
raise ContractError(f"tar contains a special member: {path.name}:{member.name}")
if member.size > MAX_MEMBER_BYTES:
raise ContractError(f"tar member exceeds size ceiling: {path.name}:{member.name}")
if member.name != required_member:
continue
if not member.isfile():
raise ContractError(f"required tar executable is not regular: {path.name}")
if require_executable_mode and stat.S_IMODE(member.mode) != 0o755:
raise ContractError(
f"required tar executable has wrong mode: {path.name}:{member.name}"
)
matches += 1
handle = archive.extractfile(member)
if handle is None:
raise ContractError(f"required tar executable is unreadable: {path.name}")
with handle:
actual_sha256, actual_size = hash_stream(
handle, declared_size=member.size, label=f"{path.name}:{member.name}"
)
if actual_sha256 != expected_sha256 or actual_size != expected_size:
raise ContractError(f"tar executable does not match selection: {path.name}")
except (tarfile.TarError, EOFError) as error:
raise ContractError(f"malformed tar archive: {path.name}") from error
if matches != 1:
raise ContractError(f"tar must contain exactly one required executable: {path.name}")
def zip_entry_kind(info: zipfile.ZipInfo) -> str:
mode = (info.external_attr >> 16) & 0xFFFF
if mode and stat.S_IFMT(mode) not in (0, stat.S_IFREG, stat.S_IFDIR):
return "special"
return "directory" if info.is_dir() else "regular"
def verify_zip(
path: pathlib.Path, *, required_member: str, expected_sha256: str,
expected_size: int, require_executable_mode: bool
) -> None:
matches = 0
try:
with zipfile.ZipFile(path, mode="r") as archive:
members = archive.infolist()
if not members or len(members) > MAX_MEMBERS:
raise ContractError(f"zip member count is invalid: {path.name}")
if sum(member.file_size for member in members) > MAX_TOTAL_MEMBER_BYTES:
raise ContractError(f"zip exceeds total member-size ceiling: {path.name}")
seen: set[str] = set()
for member in members:
name = member.filename
if not safe_member_name(name) or name in seen:
raise ContractError(f"zip has unsafe or duplicate member: {path.name}:{name}")
seen.add(name)
if zip_entry_kind(member) == "special":
raise ContractError(f"zip contains a special member: {path.name}:{name}")
if member.file_size > MAX_MEMBER_BYTES:
raise ContractError(f"zip member exceeds size ceiling: {path.name}:{name}")
if name != required_member:
continue
if zip_entry_kind(member) != "regular":
raise ContractError(f"required zip executable is not regular: {path.name}")
mode = (member.external_attr >> 16) & 0xFFFF
if require_executable_mode and stat.S_IMODE(mode) != 0o755:
raise ContractError(
f"required zip executable has wrong mode: {path.name}:{name}"
)
matches += 1
with archive.open(member, mode="r") as handle:
actual_sha256, actual_size = hash_stream(
handle, declared_size=member.file_size, label=f"{path.name}:{name}"
)
if actual_sha256 != expected_sha256 or actual_size != expected_size:
raise ContractError(f"zip executable does not match selection: {path.name}")
except (zipfile.BadZipFile, EOFError, RuntimeError) as error:
raise ContractError(f"malformed zip archive: {path.name}") from error
if matches != 1:
raise ContractError(f"zip must contain exactly one required executable: {path.name}")
def discover_archives(root: pathlib.Path) -> dict[str, pathlib.Path]:
try:
root_status = root.lstat()
except FileNotFoundError as error:
raise ContractError(f"archive directory does not exist: {root}") from error
if not stat.S_ISDIR(root_status.st_mode):
raise ContractError(f"archive path is not a regular directory: {root}")
discovered: dict[str, pathlib.Path] = {}
archive_like: list[pathlib.Path] = []
for directory, names, files in os.walk(root, followlinks=False):
parent = pathlib.Path(directory)
for name in names:
child = parent / name
if child.is_symlink():
raise ContractError(f"symlinked directory in archive tree: {child}")
for name in files:
child = parent / name
if child.is_symlink():
raise ContractError(f"symlinked file in archive tree: {child}")
if name.endswith((".tar.gz", ".zip", ".mcpb")):
archive_like.append(child)
if name in discovered:
raise ContractError(f"duplicate archive basename: {name}")
discovered[name] = child
actual_names = set(discovered)
expected_names = set(ARCHIVES)
if actual_names != expected_names or len(archive_like) != len(expected_names):
missing = sorted(expected_names - actual_names)
surplus = sorted(actual_names - expected_names)
raise ContractError(
f"archive namespace is not the exact canonical set; missing={missing}, surplus={surplus}"
)
return discovered
def main(argv: Sequence[str]) -> None:
args = parse_arguments(argv)
selections = load_selection(args.selection, require_policy=args.require_policy)
archives = discover_archives(args.archive_dir)
for name, (target, member) in ARCHIVES.items():
path = archives[name]
regular_status(path, ceiling=MAX_ARCHIVE_BYTES, label="release archive")
selection = selections[target]
verify = verify_tar if name.endswith(".tar.gz") else verify_zip
verify(
path,
required_member=member,
expected_sha256=selection["selected_sha256"],
expected_size=int(selection["selected_size"]),
require_executable_mode=not target.startswith("windows-"),
)
print(f"verified all {len(ARCHIVES)} canonical containers against selected bytes")
try:
main(sys.argv)
except (ContractError, OSError, UnicodeError, ValueError) as error:
print(f"verify-release-selection: {error}", file=sys.stderr)
raise SystemExit(1)