Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Python wheel RECORD: verify member bytes before trusting a local archive

Last updated: 30 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

A wheel RECORD lists installed paths and optional digests; checking those digests detects changed bytes for entries that declare one.

Download Python source kit

Operation contract

The fixture builds a tiny owned ZIP with a module and a RECORD row containing its SHA-256 digest in URL-safe base64. The verifier requires the row to name the expected module, rejects duplicate entries for that module and compares the digest for its bytes. It checks the clean archive, then a second archive with altered module bytes and the unchanged RECORD. This is byte-integrity verification, not package authenticity.

Failure and ownership boundary

A received archive can supply both malicious bytes and a matching malicious RECORD. Trust still needs an authenticated publisher artifact or independently obtained expected digest. This demonstration verifies one module entry, not full wheel installation rules, tags, signatures, dependency resolution or extraction safety. Python artifact hashes: verify owned bytes against a separately trusted expectation, Python wheel metadata: inspect an owned built artifact before installation and Python wheel installation: test the built artifact outside its source directory distinguish those steps.

Working program

python
import base64
import csv
import hashlib
import io
import zipfile

def archive_for(source):
    digest = base64.urlsafe_b64encode(hashlib.sha256(b"owner = 'aitrove'\n").digest()).rstrip(b"=").decode("ascii")
    record = f"receipt_tools.py,sha256={digest},18\n"
    output = io.BytesIO()
    with zipfile.ZipFile(output, "w") as wheel:
        wheel.writestr("receipt_tools.py", source)
        wheel.writestr("receipt_tools-1.0.dist-info/RECORD", record)
    return output.getvalue()

def declared_module_matches(blob):
    with zipfile.ZipFile(io.BytesIO(blob)) as wheel:
        rows = list(csv.reader(io.StringIO(wheel.read("receipt_tools-1.0.dist-info/RECORD").decode("utf-8"))))
        if len(rows) != 1 or rows[0][0] != "receipt_tools.py" or len(rows[0]) != 3 or wheel.namelist().count("receipt_tools.py") != 1:
            raise ValueError("record shape")
        digest = base64.urlsafe_b64encode(hashlib.sha256(wheel.read("receipt_tools.py")).digest()).rstrip(b"=").decode("ascii")
        return rows[0][1] == "sha256=" + digest and rows[0][2] == str(len(wheel.read("receipt_tools.py")))

print("owned bytes:", declared_module_matches(archive_for(b"owner = 'aitrove'\n")))
print("altered bytes:", declared_module_matches(archive_for(b"owner = 'other'\n")))

Output

Output
owned bytes: True
altered bytes: False

Costs and limits

Reading all archive and member bytes here uses O(n) memory and time in their byte lengths. The fixture is tiny; a production verifier must cap compressed/uncompressed sizes, entry count and duplicate paths before reading, then cover every required member. It must not extract merely to verify this field.

Common Mistakes

  • RECORD inside an untrusted archive is not an independent trust source.
  • Verifying one row does not verify every archive member.

Connected lessons

Python artifact hashes: verify owned bytes against a separately trusted expectation, Python wheel metadata: inspect an owned built artifact before installation, Python wheel installation: test the built artifact outside its source directory.

python
wheel-record-verification
Storage details