🐛 fix(sbom): keep the build machine out of the SBOMs - #3311
Merged
Merged
Conversation
gaborbernat
force-pushed
the
fix/reproducible-sbom
branch
from
September 23, 2026 01:44
843fdf9 to
d6125d3
Compare
gaborbernat
force-pushed
the
fix/reproducible-sbom
branch
from
September 23, 2026 01:58
d6125d3 to
83ee212
Compare
The wheel SBOM recorded the interpreter build string, compiler, build date, kernel, architecture and os-release of the machine that ran the build, so a rebuild on another host produced a different SBOM, serial number, RECORD and wheel hash. The provenance attestation already names the builder, so the SBOM now keeps only facts that hold for any rebuild with the same toolchain. For the same reason the build tool listing skips the INSTALLER, REQUESTED and direct_url.json files the build frontend writes, and lists tool distributions that are not pure Python without their files, since those differ per OS and architecture. The zipapp SBOM reuses that listing. The zipapp entries took the wall clock time, and the tox env dropped SOURCE_DATE_EPOCH; entries now carry that timestamp, fixed permissions and a fixed creator OS.
gaborbernat
force-pushed
the
fix/reproducible-sbom
branch
from
September 23, 2026 02:02
83ee212 to
3501127
Compare
gaborbernat
added a commit
to gaborbernat/virtualenv
that referenced
this pull request
Sep 26, 2026
The verify-release how-to said the zipapp build does not pin what it bundles, which stopped being true when pypa#3306 added pylock.zipapp.toml, and the threat model still said wheels differ between build machines after pypa#3311. Neither page covered the release attestation GitHub signs for the immutable releases published since 21.11.0. Move the examples to 21.12.1, add gh release verify and verify-asset, and give a zipapp rebuild recipe that constrains the build tools from the SBOMs. Fix the same claims in the release artifacts reference, the explanation and the threat model, whose upgrade job note also named a deploy key that the workflow does not use.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Rebuilding the 21.10.0 tag with the release's
SOURCE_DATE_EPOCHreproduces the sdist but not the wheel. The CycloneDX SBOM thathatch_build.pyembeds has recorded the build machine since #3270: the interpreter build string, compiler and build date, and atool:oscomponent with the kernel release and version, platform string, architecture and every/etc/os-releasefield. A macOS rebuild therefore gets a different SBOM, a different content-derivedserialNumber, a differentRECORDand a different wheel hash. The zipapp has the same problem, because its SBOM reuses that tool listing and every zip entry took the wall clock time. The GitHub provenance attestation already names the builder, workflow and commit, so the SBOMs do not need to repeat it. 🔒The SBOMs now keep only facts that hold for any rebuild of the same source with the same toolchain: components, hashes, licences, purls, the source commit, the Python version number, the build backend and its dependencies with their files, and the
SOURCE_DATE_EPOCHtimestamp. The serial number stays derived from the content, so it is now stable across rebuilds. Two more inputs varied by host once the OS fields were gone. Build tool distributions installed from platform wheels (such aspyyamlandrpds-pyin the zipapp build environment) list different.sofiles per OS and architecture, so they keep name and version but lose their file list. The build frontend writesINSTALLER,REQUESTEDanddirect_url.json, which the listing now skips.make_zipapp.pywrites each entry with theSOURCE_DATE_EPOCHtimestamp, mode0644and a fixed creator OS. Thezipapptox environment now passesSOURCE_DATE_EPOCHthrough; before, it dropped the variable and fell back to hatchling's 2020 default.cyclonedx_to_spdx.pydrops its now unusedoperating-systempurpose mapping.✅ I checked this on commit 81fc2ceb with
SOURCE_DATE_EPOCH=1790127591, CPython 3.14.7 and hatchling 1.32.4 (build backend dependencies pinned from the first build's SBOM). The wheel built withuv buildon macOS arm64, withuv buildin a Linux aarch64 container and withpython -m build(pip) in a Linux x86_64 container has the same SHA-256 on all three (1c0430d6…90ed), andcmpreports no difference.tox r -e zipappon macOS and in the Linux container produced a byte-identicalvirtualenv.pyzandvirtualenv.pyz.cdx.json. A byte-for-byte wheel rebuild needs the same source tree (a git checkout, since the SBOM records the commit),SOURCE_DATE_EPOCH, Python patch version, and build backend version along with its dependencies. The build frontend, OS and architecture can differ. The zipapp needs those inputs too. Since #3306 its bundled distributions come from the committed PEP 751 lock, and the tools that build the wheel inside it must match the versions its SBOM lists, since that build resolves its own isolated environment.The docs from #3305 said the wheel differs from a rebuild in its SBOM and
RECORD. They now list the inputs a byte-for-byte rebuild needs, and the how-to shows how to read the Python version and build constraints from the published SBOM and compare the rebuilt wheel withcmp. Wheels up to 21.10.0 keep the old SBOM and still differ on rebuild.