Files
ARCADIA-Studio/harness/experiments/prerelease37-package-integrity-20260924/inspect_studio_package.py
T

93 lines
5.2 KiB
Python

"""Inspect a downloaded Windows ZIP without extracting or running its contents.
Expected archive/binary hashes must come from separately reviewed build evidence.
This checks artifact integrity and provenance assertions, not native acceptance.
"""
import argparse
import hashlib
import json
from pathlib import Path, PurePosixPath
import re
import stat
import zipfile
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('archive', type=Path)
for option in ('archive-sha256', 'source', 'version', 'desktop-sha256', 'server-sha256', 'runtime-sha256'):
parser.add_argument('--' + option, required=True)
parser.add_argument('--documents-receipt', required=True, type=Path)
args = parser.parse_args()
def sha(raw):
return hashlib.sha256(raw).hexdigest()
for value in (args.archive_sha256, args.desktop_sha256, args.server_sha256, args.runtime_sha256):
assert re.fullmatch(r'[0-9a-f]{64}', value), 'Expected lowercase SHA256 required'
assert re.fullmatch(r'[0-9a-f]{40}', args.source), 'Exact source commit required'
assert sha(args.archive.read_bytes()) == args.archive_sha256, 'Archive checksum differs'
target = 'x86_64-pc-windows-gnullvm'
root = f'ArcadiaStudio-{args.version}-{target}/'
expected_binaries = {'arcadia-studio.exe': args.desktop_sha256,
'arcadia-server.exe': args.server_sha256,
'libunwind.dll': args.runtime_sha256}
with zipfile.ZipFile(args.archive) as archive:
infos = archive.infolist()
assert sum(row.file_size for row in infos) <= 1024 ** 3, 'Unexpected package size'
assert len({row.filename.casefold() for row in infos}) == len(infos), 'Duplicate/case-colliding archive members'
files = {}
for row in infos:
name = row.filename
path = PurePosixPath(name)
assert not path.is_absolute() and '..' not in path.parts and '\\' not in name and ':' not in name
assert name.startswith(root), 'Unexpected archive root'
assert not stat.S_ISLNK(row.external_attr >> 16), 'Symlink archive member'
if not row.is_dir():
relative = name[len(root):]
assert relative and relative not in files
files[relative] = sha(archive.read(row))
def read(name):
assert name in files, 'Missing required package member: ' + name
return archive.read(root + name)
checksums = {}
for line in read('SHA256SUMS.txt').decode('utf-8-sig').splitlines():
match = re.fullmatch(r'([0-9a-f]{64}) (.+)', line)
assert match, 'Malformed package checksum line'
digest, name = match.groups()
assert name not in checksums, 'Duplicate package checksum path'
checksums[name] = digest
assert set(files) == set(checksums) | {'SHA256SUMS.txt'}, 'Missing or unexpected package files'
assert 'SHA256SUMS.txt' not in checksums
assert all(files[name] == digest for name, digest in checksums.items()), 'Package file checksum differs'
metadata = json.loads(read('package.json'))
assert metadata['version'] == args.version and metadata['target'] == target
for name, digest in metadata['input_sha256'].items():
assert files.get(name) == digest, 'Package input differs: ' + name
for name, digest in expected_binaries.items():
assert files.get(name) == metadata['input_sha256'].get(name) == digest, 'Build artifact differs: ' + name
build = metadata['build_evidence']['evidence']
assert build['source_identity'] == args.source, 'Source assertion needs exact commit or explicit review'
assert build['profile'] == 'dev' and build['target'] == target and build['compiler']
for name in ('arcadia-studio.exe', 'arcadia-server.exe'):
assert build['binary_sha256'][name] == expected_binaries[name]
for name in ('README.md', 'START-HERE.txt', 'LICENSE', 'docs/windows-packaging.md',
'docs/preview-human-checks.md', 'THIRD-PARTY-NOTICES.txt', 'dependency-notices.json'):
read(name)
document_check = json.loads(read('documentation-check.json'))
assert 'checked_local_document_links' in document_check
notices = json.loads(read('dependency-notices.json'))
assert notices['target'] == target and notices['runtime']['sha256'] == args.runtime_sha256
documents = json.loads(args.documents_receipt.read_text(encoding='utf-8-sig'))
assert documents['result'] == 'PASS' and len(documents['files']) == documents['file_count']
for row in documents['files']:
assert files.get(row['package_path']) == row['sha256'], 'Reviewed document bytes differ: ' + row['package_path']
print(json.dumps({'archive': args.archive.name, 'archive_sha256': args.archive_sha256,
'version': args.version, 'source_assertion': args.source,
'package_files': len(files), 'uncompressed_bytes': sum(row.file_size for row in infos),
'verified_binary_sha256': expected_binaries,
'reviewed_document_files': len(documents['files']),
'document_links': document_check['checked_local_document_links'],
'compiler_assertion': build['compiler'], 'imports': metadata['imports'],
'scope': 'Archive integrity, supplied build hashes and reviewed document bytes; no binary execution or native acceptance.'}, indent=2))