Software Bill of Materials (SBOM)

A Software Bill of Materials (SBOM) is a document used to exchange information about software and its composition. This format is primarily used in the security field to check software and its dependencies for vulnerabilities using vulnerability databases such as CVE and OSV. The SBOM format used by the CPython project is SPDX, which can be converted to other formats as needed. The SBOM file for the dependencies included in CPython is maintained at Misc/sbom.spdx.json. The file is generated using Tools/build/generate_sbom.py.

Generating an SBOM File

… with uv

uv provides an easy way to generate an SBOM file in the CycloneDX v1.5 format using:

$ uv export --format='cyclonedx1.5' > sbom.cdx.json

However, the file contains only very basic information; for example, for cusy.tasks:

"component": {
  "type": "library",
  "bom-ref": "cusy-tasks-1@26.2.0",
  "name": "cusy-tasks",
  "version": "26.2.0",
  "properties": [
    {
      "name": "uv:package:is_project_root",
      "value": "true"
    }
  ]
}

Using uv export --all-groups --format='cyclonedx1.5' > sbom.cdx.json, you can also include all dependency groups in the SBOM file.

… using CycloneDX Python

The output from CycloneDX Python is considerably more comprehensive:

{
  "bom-ref": "cusy-tasks==26.2.0",
  "description": "",
  "externalReferences": [
    {
      "comment": "PackageSource: Local",
      "type": "distribution",
      "url": "file:///Users/veit/cusy/prj/cusy.tasks"
    },
    {
      "comment": "from packaging metadata Project-URL: Documentation",
      "type": "documentation",
      "url": "https://tasks.cusy.io/"
    },
    {
      "comment": "from packaging metadata Project-URL: Mastodon",
      "type": "other",
      "url": "https://mastodon.social/@Python4DataScience"
    },
    {
      "comment": "from packaging metadata Project-URL: GitHub",
      "type": "vcs",
      "url": "https://github.com/cusyio/cusy.tasks"
    }
  ],
  "licenses": [
    {
      "license": {
        "acknowledgement": "declared",
        "id": "BSD-3-Clause"
      }
    }
  ],
  "name": "cusy-tasks",
  "type": "library",
  "version": "26.2.0"
}

The command to generate the file is:

$ uvx --from cyclonedx-bom cyclonedx-py environment .venv --output-file sbom.cdx.json

Warning

CycloneDX Python generates the SBOM file from the current .venv directory. So if you run cyclonedx-bom in your development environment, you will also find all your development tools listed in your SBOM file.

… with sbomify

sbomify provides a GitHub Action for generating the SBOM file, which uses CycloneDX Python under the bonnet. The file can then be attested using actions/attest-sbom:

.github/workflows/sbomify.yml
---
name: Build with SBOM Attestation

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write
  attestations: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false

      - name: Build
        run: uv build

      - name: Generate SBOM
        uses: sbomify/sbomify-action@f38411f20fe2cc52e5bb7abd4d094ffc9dc5f17c # v26.7.0
        env:
          LOCK_FILE: uv.lock
          OUTPUT_FILE: sbom.cdx.json
          COMPONENT_NAME: myapp
          COMPONENT_VERSION: ${{ github.sha }}
          ENRICH: true
          UPLOAD: false

      - name: Attest SBOM
        uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
        with:
          subject-path: './dist'
          sbom-path: './sbom.cdx.json'

In GitLab CI, you can use sbomify as follows:

.gitlab-ci.yml
stages:
  - build
  - sbom

build:
  stage: build
  script:
    - uv build
  artifacts:
    paths:
      - dist/

generate-sbom:
  stage: sbom
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: sbom.cdx.json
    COMPONENT_NAME: myapp
    COMPONENT_VERSION: $CI_COMMIT_TAG
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - /sbomify.sh
  artifacts:
    paths:
      - sbom.cdx.json
    reports:
      cyclonedx: sbom.cdx.json

You can also integrate dependency scanning:

.gitlab-ci.yml
include:
  - template: Security/Dependency-Scanning.gitlab-ci.yml

generate-sbom:
  stage: test
  image: ghcr.io/sbomify/sbomify-action
  variables:
    LOCK_FILE: uv.lock
    OUTPUT_FILE: gl-sbom-report.cdx.json
    UPLOAD: "false"
    ENRICH: "true"
  script:
    - /sbomify.sh
  artifacts:
    paths:
      - gl-sbom-report.cdx.json
    reports:
      cyclonedx: gl-sbom-report.cdx.json

PEP 770 – SBOMs in Python packages ———————————~

PEP 770 standardises the inclusion of SBOMs in Python wheels via the .dist-info/sboms/ directory. If you publish Python packages on PyPI, this means that your users will automatically receive the SBOM file when they install your package, for example:

myapp-26.2.0.dist-info
├── METADATA
├── RECORD
└── sboms/
    └── myapp.cdx.json

Build backends such as Hatchling ≥ 1.28 already support this via the sbom-files configuration:

pyproject.toml
[tool.hatch.build.targets.wheel]
sbom-files = ["myapp.cdx.json"]

Instead of generating the SBOM file from scratch during the build, a minimal CycloneDX SBOM file can be checked into the repository:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "metadata": {
    "component": {
      "type": "library",
      "name": "mylib",
      "version": "0.0.0-placeholder"
    }
  },
  "components": []
}

In the CI workflow, the placeholder SBOM file is then checked out along with the code. sbomify then enriches it with the latest information. Hatchling subsequently builds the wheel using the latest SBOM file, and finally the wheel is published to PyPI.

Analysis

There are numerous tools available for security and licence checks, each focusing on different problem areas. Two open-source tools for SBOM analysis are Dependency Track and GUAC. With sbomify-action, you can upload SBOMs directly from the CI pipeline to your Dependency Track instance using:

UPLOAD_DESTINATIONS=dependency-track