python-sbom

Every wheel you ship, with its SBOM inside.

A build plugin that writes a software bill of materials (SBOM) in the CycloneDX format into each wheel your build backend makes, where PEP 770 (a Python Enhancement Proposal) tells installers and scanners to look for it.

python-sbom writes a CycloneDX 1.6 SBOM into every wheel you build, at <name>-<version>.dist-info/sboms/<name>.cdx.json. That is where PEP 770 tells installers to keep SBOMs, so they land in the environment next to the installed package, where scanners look for them.

General-purpose scanners read the dependencies a package declares or the packages an environment has installed. They cannot see a wheel your project ships as a data file, and they cannot say how the wheel was built. The SBOM python-sbom writes records both. The Changelog lists the changes in each version.

Every build backend

A plugin for hatchling, pdm-backend and setuptools, and a wrapper backend for meson-python, maturin and the rest.

Supported backends
How the wheel was built

The source commit, the interpreter and every build tool down to its files, so a third party can rebuild the wheel and compare the bytes.

Reproducibility
What the wheel bundles

Each wheel a project ships as data, with its hashes, license, files and the packages it vendors.

Bundled wheels
Checked before upload

python-sbom validate checks every SBOM against the CycloneDX schema in CI, and spdx renders it as SPDX 2.3.

Check SBOMs in CI

A build, the SBOM it wrote, and the check a CI job runs before publishing:

$ uv build
Successfully built dist/greet-1.0.tar.gz
Successfully built dist/greet-1.0-py3-none-any.whl
$ unzip -Z1 dist/greet-1.0-py3-none-any.whl | grep sboms
greet-1.0.dist-info/sboms/greet.cdx.json
$ python-sbom validate dist/ --allow-license MIT
SBOM in greet-1.0-py3-none-any.whl is valid

Install

python-sbom runs inside your build, so it goes in [build-system] requires with the extra for your backend, and a [tool.python-sbom] table names you as the supplier. It needs Python 3.11 or later.

pyproject.toml
[build-system]
build-backend = "hatchling.build"
requires = [ "hatchling>=1.28", "python-sbom[hatch]>=0.3" ]

[project]
name = "greet"
version = "1.0"
description = "says hello"
license = "MIT"
license-files = [ "LICENSE" ]
requires-python = ">=3.11"
classifiers = [
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Programming Language :: Python :: 3.15",
]
dependencies = [ "packaging>=24" ]
urls.Source = "https://github.com/you/greet"

[tool.hatch]
build.hooks.python-sbom = {}

[tool.python-sbom]
supplier = { name = "You", url = [ "https://github.com/you" ] }
pyproject.toml
[build-system]
build-backend = "pdm.backend"
requires = [ "pdm-backend>=2.4", "python-sbom[pdm]>=0.3" ]

[project]
name = "greet"
version = "1.0"
description = "says hello"
license = "MIT"
requires-python = ">=3.11"
classifiers = [
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Programming Language :: Python :: 3.15",
]
dependencies = [ "packaging>=24" ]

[tool.python-sbom]
supplier = { name = "You", url = [ "https://github.com/you" ] }
pyproject.toml
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "python-sbom[setuptools]>=0.3", "setuptools>=77" ]

[project]
name = "greet"
version = "1.0"
description = "says hello"
license = "MIT"
requires-python = ">=3.11"
classifiers = [
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Programming Language :: Python :: 3.15",
]
dependencies = [ "packaging>=24" ]

[tool.python-sbom]
supplier = { name = "You", url = [ "https://github.com/you" ] }
pyproject.toml
[build-system]
build-backend = "python_sbom.backend"
requires = [ "meson>=1.6", "meson-python>=0.18", "ninja", "python-sbom>=0.3" ]

[project]
name = "greet"
version = "1.0"
description = "says hello"
license = "MIT"
requires-python = ">=3.11"
classifiers = [
  "Programming Language :: Python :: 3 :: Only",
  "Programming Language :: Python :: 3.11",
  "Programming Language :: Python :: 3.12",
  "Programming Language :: Python :: 3.13",
  "Programming Language :: Python :: 3.14",
  "Programming Language :: Python :: 3.15",
]
dependencies = [ "packaging>=24" ]

[tool.python-sbom]
build-backend = "mesonpy"
supplier = { name = "You", url = [ "https://github.com/you" ] }

Every wheel you build from then on carries the SBOM. How-to guides covers each backend in detail.

The command line, which describes wheels built elsewhere and checks SBOMs, installs as a tool:

uv tool install 'python-sbom[validate]'
python-sbom --help
pipx install 'python-sbom[validate]'
python-sbom --help

Where to go next

New to python-sbom?

The tutorial builds a first package with an SBOM, reads it, and checks it.

Your first SBOM
Have a specific task?

Each guide sets python-sbom up for one backend or handles one kind of artifact.

How-to guides
Looking up an option?

The backends, the configuration, the command line and the Python API.

Reference
Want to know how it works?

Why each backend gets its integration, what the SBOM records, and how the code is laid out.

How it works