✨ feat(build): ship a CycloneDX SBOM in every wheel - #3265
Merged
Merged
Conversation
Downstream consumers scanning for bundled software cannot see the pip and setuptools wheels virtualenv embeds under src/virtualenv/seed/wheels/embed: syft, cyclonedx-py and GitHub's own dependency-graph export all read declared dependency metadata or an installed environment, and these wheels are neither. They are data files, invisible to every one of those tools, verified empirically against a real build of this project. PEP 770 exists for exactly this case. A build hook now writes a CycloneDX 1.6 document into the wheel's .dist-info/sboms/ directory, declaring each bundled wheel with its name, version, licence and the SHA-256 already computed for integrity checking at install time. The hook reads BUNDLE_SUPPORT and BUNDLE_SHA256 straight out of the existing embed/__init__.py with ast.literal_eval, the same approach tasks/upgrade_wheels.py already uses, so a wheel bump keeps the SBOM current with no separate update step. No scanner runs in CI. Every one tested against this repository either found nothing or a component list that omitted the four things worth declaring, so adding one would add a dependency and an upgrade treadmill to reproduce data pyproject.toml already states. hatchling>=1.28 is required for sbom-files support, but that release also dropped Python 3.9 from hatchling itself, which is still a supported build environment here. build-system.requires now splits on python_version so anything older stays on hatchling<1.28, and the hook checks for the sbom_files key before touching it rather than assuming the newer hatchling ran, so those builds proceed without an SBOM instead of failing. The release workflow extracts the SBOM from the built wheel and attests it against the sdist and wheel with actions/attest, verified against the action's own documented required permissions.
gaborbernat
force-pushed
the
sbom-attestation
branch
from
September 19, 2026 07:42
2c38135 to
9f37ef7
Compare
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.
Downstream consumers scanning for bundled software cannot see the pip and setuptools wheels virtualenv embeds under
src/virtualenv/seed/wheels/embed. 📦 I tested syft, cyclonedx-py, and GitHub's own dependency-graph export against a real build of this project: each one reads declared dependency metadata or an installed environment, and these wheels are neither. They are data files, invisible to every tool tried, so anyone auditing what a virtualenv install actually contains has no way to find them today.PEP 770 exists for exactly this case.
hatch_build.pyadds a hatchling build hook that writes a CycloneDX 1.6 document into the wheel's.dist-info/sboms/directory, declaring each bundled wheel with its name, version, licence, and the SHA-256 already computed for integrity checking at install time. The hook readsBUNDLE_SUPPORTandBUNDLE_SHA256straight out of the existingembed/__init__.pywithast.literal_eval, the same approachtasks/upgrade_wheels.pyalready uses to regenerate those tables, so a wheel bump keeps the SBOM current without a separate update step.No scanner runs in CI. Every one I tested against this repository either found nothing or a component list that omitted the four things actually worth declaring, so adding one here would add a dependency and an upgrade treadmill just to reproduce data
pyproject.tomlalready states. 🔍hatchling>=1.28is now required, since that is the release that addedsbom-filessupport. The release workflow extracts the SBOM from the built wheel and attests it against the sdist and wheel withactions/attest, whose required permissions (id-token,attestations,artifact-metadata) I checked against the action's own documentation rather than assume, and whose pinned SHA I resolved from its real tags rather than copy from a neighbouring step.