Software Bill of Materials (SBOM)¶
Every official bocpy wheel ships with a Software Bill of Materials
(SBOM) embedded inside the distribution itself, following
PEP 770. The SBOM is a
machine-readable inventory of what the wheel contains and how it was
built, suitable for consumption by supply-chain tooling such as
grype,
Dependency-Track, or
Trivy.
At a glance¶
Format |
|
Location inside the wheel |
|
Filename convention |
PEP 770: |
Generator |
scripts/build_sbom.py (stdlib-only) |
Generator version |
Recorded in |
Validator |
scripts/validate_sbom.py (stdlib-only structural validator) |
What the SBOM contains¶
bocpy has zero third-party runtime Python dependencies: it ships
only stdlib usage and its own C extensions. The SBOM therefore
describes a single root component — the wheel itself — and an empty
components list:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:…",
"version": 1,
"metadata": {
"timestamp": "2026-…",
"tools": {
"components": [
{
"type": "application",
"name": "build_sbom.py",
"version": "0.1.0",
"vendor": "Microsoft"
}
]
},
"component": {
"bom-ref": "pkg:pypi/bocpy@<version>",
"type": "library",
"name": "bocpy",
"version": "<version>",
"purl": "pkg:pypi/bocpy@<version>",
"description": "…",
"licenses": [{"license": {"id": "MIT"}}],
"supplier": {"name": "Microsoft", "url": ["…homepage…"]},
"externalReferences": [
{"type": "website", "url": "…homepage…"},
{"type": "vcs", "url": "…repo…"}
],
"properties": [
{"name": "cdx:python:git_commit", "value": "<sha>"},
{"name": "cdx:python:wheel_filename", "value": "<wheel filename>"}
]
}
},
"components": [],
"dependencies": [
{"ref": "pkg:pypi/bocpy@<version>", "dependsOn": []}
]
}
Two custom properties are attached to the root component:
cdx:python:git_commitThe git commit SHA the wheel was built from. Reproduces the exact source tree behind the wheel.
cdx:python:wheel_filenameThe basename of the wheel the SBOM was embedded in (e.g.
bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl). The CycloneDXpurlfield intentionally does not encode the wheel tag, so this property gives consumers the exact filename when they need it.
Native shared libraries that auditwheel, delocate, or
delvewheel bundle into the wheel are not currently enumerated
in the SBOM components list. Their presence is recoverable from
the wheel zip itself; future versions of the SBOM may add explicit
entries.
Extracting the SBOM from a wheel¶
The SBOM is a plain file inside the wheel, so any zip extractor works:
$ unzip -p bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl \
'bocpy-0.6.0.dist-info/sboms/bocpy.cdx.json' \
| python -m json.tool | head -20
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
…
}
For automated use, python -m zipfile works without any third-party
dependency:
$ python -m zipfile -e bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl /tmp/extracted/
$ cat /tmp/extracted/bocpy-0.6.0.dist-info/sboms/bocpy.cdx.json
Verifying the SBOM yourself¶
The release workflow runs two checks on every batch of wheels before they are published — you can reproduce both locally.
1. Structural validation. scripts/validate_sbom.py is
stdlib-only and pins the invariants bocpy commits to (CycloneDX 1.6,
urn:uuid:<v4> serial, ISO 8601 Z-suffix timestamp, root
purl == bom-ref and both starting with pkg:pypi/bocpy@, etc.):
$ python scripts/validate_sbom.py path/to/bocpy.cdx.json
OK path/to/bocpy.cdx.json
The validator also accepts a directory of *.cdx.json files:
$ python scripts/validate_sbom.py sboms/
OK sboms/bocpy-0.6.0-cp310-cp310-manylinux_x86_64.cdx.json
OK sboms/bocpy-0.6.0-cp314-cp314-linux_x86_64.cdx.json
2. Vulnerability scan. Any third-party SBOM-aware scanner will parse the embedded file. With grype:
$ grype sbom:./bocpy-0.6.0.dist-info/sboms/bocpy.cdx.json
No vulnerabilities found
CI in .github/workflows/build_wheels.yml runs the same two checks
in the verify_sboms job after every successful wheel build. The
job is configured with --fail-on high: a HIGH or CRITICAL finding
fails the workflow.
Regenerating an SBOM by hand¶
The release workflow drives scripts/build_sbom.py via
cibuildwheel’s repair step, but the script is also runnable
standalone for testing or for downstream re-packaging:
$ python scripts/build_sbom.py generate \
--wheel-filename bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl \
--git-commit "$(git rev-parse HEAD)" \
> bocpy.cdx.json
$ python scripts/build_sbom.py inject \
path/to/bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl
The inject subcommand rewrites the wheel’s RECORD to add the
new SBOM entry atomically (writes a temporary wheel alongside, then
renames over the original only after the temp file is fully flushed
and closed). An optional --copy-to DIR mode performs the
injection into a copy of the wheel in DIR, leaving the original
untouched — this is how the Windows CIBW_REPAIR_WHEEL_COMMAND_WINDOWS
moves repaired wheels to the cibuildwheel {dest_dir}.
Wheel integrity validation¶
Because build_sbom.py inject rewrites every wheel’s RECORD
file after auditwheel / delocate / delvewheel have already
rewritten the ZIP, every release candidate goes through one more gate
before it can ship:
$ python scripts/validate_wheel.py path/to/wheelhouse/
OK wheelhouse/bocpy-0.7.0-cp314-cp314-manylinux_2_28_x86_64.whl
OK wheelhouse/bocpy-0.7.0-cp314-cp314-win_amd64.whl
scripts/validate_wheel.py is a thin CLI driver over
scripts/_vendored_warehouse_wheel.py — a stdlib-only,
verbatim-vendored copy of PyPI / Warehouse’s own
validate_record and validate_entrypoints. Running it locally
is the only reliable way to predict whether PyPI will accept a wheel:
twine check only validates long_description, wheel unpack
only checks RECORD hashes and sizes, and check-wheel-contents only
checks layout — none of them runs PyPI’s actual acceptance code.
The vendored file’s docstring records the exact upstream commit it
was synced from and the refresh procedure. The wheel-integrity job
in .github/workflows/build_wheels.yml runs it twice per release:
once inside CIBW_REPAIR_WHEEL_COMMAND (so a per-platform build
fails immediately on any defect) and once again in the merge job
(defense in depth — the last gate before the wheels artifact is
uploaded).
Note
Per-component hash drift between 0.7.0 and 0.8.0+: _math.*.so
is now compiled with -O3 (was -O2 in 0.7.0) so its
SHA-256 in any auditor’s component diff will change even when
nothing else moved. The flag is pinned in setup.py and scoped
to _math only; _core.*.so is unaffected. See the comment
in setup.py for the rationale (-fvect-cost-model=very-cheap
at -O2 declines to vectorise the M1 aggregate kernels).
See also¶
API — bocpy’s public Python API.
SUPPLY_CHAIN.md — the full supply-chain hardening policy, covering hashed
ci/constraints-*.txtfiles, SHA-pinned GitHub Actions, thepip-auditjob, and the downstream consumer template.PEP 770 — Embedding SBOMs in Python wheels.