import hashlib import io import tarfile import pytest from scripts import build_mcp_hub_release_images as producer def _fixture( monkeypatch, *, declared_size=None, duplicate=False, kind=tarfile.REGTYPE, machine=62, bad_archive=False, bad_binary=False, member_size=None, mode=0o755, extra_stream=False, ): header = bytearray(64) header[:7] = b"\x7fELF\x02\x01\x01" header[16:18] = (2).to_bytes(2, "little") header[18:20] = machine.to_bytes(2, "little") binary = bytes(header) + b"synthetic public scanner fixture" downloaded = b"synthetic compressed archive" monkeypatch.setattr(producer.platform, "system", lambda: "Linux") monkeypatch.setattr(producer.platform, "machine", lambda: "x86_64") monkeypatch.setattr( producer, "TRIVY_ARCHIVE_SHA256", "0" * 64 if bad_archive else hashlib.sha256(downloaded).hexdigest(), raising=False, ) monkeypatch.setattr( producer, "TRIVY_BINARY_SHA256", "0" * 64 if bad_binary else hashlib.sha256(binary).hexdigest(), raising=False, ) monkeypatch.setattr( producer, "TRIVY_BINARY_BYTES", len(binary) if declared_size is None else declared_size, raising=False, ) class Opener: def open(self, request, timeout): assert request.full_url == producer.TRIVY_URL return io.BytesIO(downloaded) member = tarfile.TarInfo("trivy") member.size = ( (len(binary) if declared_size is None else declared_size) if member_size is None else member_size ) member.mode = mode member.type = kind class Bundle: def __enter__(self): return self def __exit__(self, *_args): return False def getmembers(self): return [member, member] if duplicate else [member] def extractfile(self, _member): return io.BytesIO(binary + b"unexpected" if extra_stream else binary) monkeypatch.setattr(producer.urllib.request, "build_opener", lambda *_args: Opener()) monkeypatch.setattr(producer.tarfile, "open", lambda *_args: Bundle()) return binary def test_verified_official_asset_contract_accepts_exact_binary(monkeypatch, tmp_path): binary = _fixture(monkeypatch) output = producer._download_trivy(tmp_path) assert output.read_bytes() == binary def test_actual_public_member_size_advances_to_strict_length_check(monkeypatch, tmp_path): _fixture(monkeypatch, declared_size=166486178) with pytest.raises(producer.BuildRejected, match="executable length differs"): producer._download_trivy(tmp_path) def test_compressed_download_bound_remains_independent_and_enforced(monkeypatch, tmp_path): assert producer.MAX_TRIVY_ARCHIVE_BYTES == 128 * 1024**2 _fixture(monkeypatch) monkeypatch.setattr(producer, "MAX_TRIVY_ARCHIVE_BYTES", 1) with pytest.raises(producer.BuildRejected, match="download exceeded its size bound"): producer._download_trivy(tmp_path) @pytest.mark.parametrize( "case", [ "archive", "binary", "duplicate", "symlink", "architecture", "size", "mode", "oversized_stream", ], ) def test_scanner_trust_guards_remain_closed(monkeypatch, tmp_path, case): _fixture( monkeypatch, bad_archive=case == "archive", bad_binary=case == "binary", duplicate=case == "duplicate", kind=tarfile.SYMTYPE if case == "symlink" else tarfile.REGTYPE, machine=183 if case == "architecture" else 62, member_size=999 if case == "size" else None, mode=0o4755 if case == "mode" else 0o755, extra_stream=case == "oversized_stream", ) with pytest.raises(producer.BuildRejected): producer._download_trivy(tmp_path)