.. _sbom: 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 ----------- .. list-table:: :widths: 30 70 :header-rows: 0 * - Format - `CycloneDX 1.6 JSON `_ * - Location inside the wheel - ``-.dist-info/sboms/bocpy.cdx.json`` * - Filename convention - PEP 770: ``.cdx.json`` (no further suffix) * - Generator - `scripts/build_sbom.py `_ (stdlib-only) * - Generator version - Recorded in ``metadata.tools.components[].version`` * - 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: .. code-block:: json { "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@", "type": "library", "name": "bocpy", "version": "", "purl": "pkg:pypi/bocpy@", "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": ""}, {"name": "cdx:python:wheel_filename", "value": ""} ] } }, "components": [], "dependencies": [ {"ref": "pkg:pypi/bocpy@", "dependsOn": []} ] } Two custom properties are attached to the root component: ``cdx:python:git_commit`` The git commit SHA the wheel was built from. Reproduces the exact source tree behind the wheel. ``cdx:python:wheel_filename`` The basename of the wheel the SBOM was embedded in (e.g. ``bocpy-0.6.0-cp314-cp314-manylinux_2_28_x86_64.whl``). The CycloneDX ``purl`` field 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: .. code-block:: console $ 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: .. code-block:: console $ 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:`` serial, ISO 8601 ``Z``-suffix timestamp, root ``purl == bom-ref`` and both starting with ``pkg:pypi/bocpy@``, etc.): .. code-block:: console $ 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: .. code-block:: console $ 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: .. code-block:: console $ 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: .. code-block:: console $ 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: .. code-block:: console $ 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 -------- * :ref:`api` — bocpy's public Python API. * `SUPPLY_CHAIN.md `_ — the full supply-chain hardening policy, covering hashed ``ci/constraints-*.txt`` files, SHA-pinned GitHub Actions, the ``pip-audit`` job, and the downstream consumer template. * `PEP 770 `_ — Embedding SBOMs in Python wheels. * `CycloneDX 1.6 specification `_.