Skip to content
cncfPublic

About

House of SBOMs (and tooling)

Resources

Code of conduct

Stars

13 stars

Watchers

1 watching

Forks

Latest commit

 

History

2,082 Commits

Folders and files

Repository files navigation

CNCF Projects Supply Chain & SBOM Generator

⚠️ DISCLAIMER

IMPORTANT NOTICE:

The Software Bill of Materials (SBOM) files generated by this project are automatically generated by the CNCF and are NOT official SBOMs provided by the individual CNCF projects themselves.

  1. No Warranty: These SBOMs are provided "AS IS" without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and noninfringement.

  2. No Guarantee of Completeness or Accuracy: We make no representations or guarantees regarding the completeness, accuracy, reliability, or currentness of the information contained in these SBOMs. The automated generation process may miss dependencies, include incorrect versions, or contain other errors.

  3. Use at Your Own Risk: Any use of these SBOMs is entirely at your own risk. This project, its contributors, and maintainers shall not be liable for any claims, damages, or other liability arising from the use of these SBOMs.

  4. Not a Substitute for Official SBOMs: For production use, compliance requirements, or security audits, please refer to the official documentation and releases of each individual CNCF project.

  5. Automated Generation: These SBOMs are generated using Waybill (formerly Mikebom) with deps.dev enrichment for license and dependency resolution. The generation process runs weekly and supports all ecosystems that Waybill can detect (Go, Rust, npm, Python, Java/Maven, Ruby, and more).


Overview

This tool generates Software Bill of Materials (SBOM) in SPDX JSON format for CNCF projects and uploads them to OCI-compatible S3 storage.

The SBOM generator:

  • Automatically syncs the list of CNCF projects from the CNCF Landscape
  • Discovers additional subproject repositories within CNCF GitHub organizations
  • Fetches stable releases (major, minor, patch) from CNCF project repositories
  • Supports all ecosystems with lockfile-aware dependency graph extraction (Go, Rust, npm, Python, Maven, Ruby, etc.)
  • Skips alpha, beta, RC, and other pre-release versions
  • Generates SPDX 2.3-compliant SBOM files using Waybill (v0.10.0)
  • Automatically enriches SBOMs with license information via deps.dev and ClearlyDefined
  • Extracts real dependency graph edges (not a flat fan-out) from lockfiles
  • Uploads SBOMs directly to OCI S3-compatible buckets (no SBOM files are stored in this repository)

Storage

Generated SBOMs are stored in two OCI S3-compatible buckets:

Bucket Content
cncf-project-sboms Official CNCF project SBOMs
cncf-subproject-sboms Subproject / discovered repo SBOMs

S3 Object Naming Convention

A CNCF project has one folder name in both buckets: <project> is the slug of its CNCF landscape name (lowercase, spaces become -, every other character outside [a-z0-9-] is dropped), e.g. Argo → argo, Aeraki Mesh → aeraki-mesh, Open Policy Agent (OPA) → open-policy-agent-opa.

Projects (cncf-project-sboms):

<project>/<version>/<project>_<version>_spdx.json

Example: argo/3.3.14/argo_3_3_14_spdx.json

Subprojects (cncf-subproject-sboms), with the same <project> slug as their parent:

<project>/<subproject>/<version>/<project>_<subproject>_<version>_spdx.json

Example: argo/argo-workflows/4.1.4/argo_argo-workflows_4_1_4_spdx.json

The parent of a subproject is recorded in util/data/discovered-repos.yaml as parent_project (landscape name) and parent_repo (the project's main repository), next to discovered_by, which is kept for provenance. When parent_project is missing, discovered_by's owner/repo is looked up in cncf-projects.yaml. Only if the parent is no longer in the landscape is the parent's repository name used, as before. All writers (workflows, ingest-sbom-oci.sh, migrate-subproject-keys.sh) derive these names from util/sbom-lib.sh, so they cannot drift apart.

Note: Dots in version numbers are replaced with underscores in the filename to avoid potential issues.

Migration: subproject SBOMs uploaded before this convention live under the parent's repository name (e.g. argo-cd/argo-workflows/…, aeraki/meta-protocol-proxy/…). util/migrate-subproject-keys.sh moves them; see Utility Tools.

Repository Structure

This repository contains only tooling and configuration — no SBOM data files.

./
├── README.md                           # This file
├── .gitignore                          # Excludes generated SBOM data
├── .github/workflows/
│   ├── sync-cncf-projects.yml          # Sync CNCF project list from landscape
│   ├── discover-cncf-repos.yml         # Discover subproject repos in CNCF orgs
│   ├── generate-sbom.yml               # Generate SBOMs and upload to S3
│   ├── generate-tooling-sbom.yml       # Generate SBOMs for this repo's tooling and CI chain
│   ├── watch-sandbox-applications.yml  # One-time SBOMs for CNCF sandbox applications
│   └── migrate-sboms-to-oci.yml        # One-time migration of legacy repo SBOMs to S3
└── util/
    ├── data/
    │   ├── cncf-projects.yaml          # Auto-synced CNCF project list (DO NOT EDIT)
    │   ├── repositories.yaml          # Manually maintained additional projects
    │   └── discovered-repos.yaml       # Subproject repos found in CNCF orgs (DO NOT EDIT)
    ├── extract-projects/               # Go tool to sync projects from CNCF landscape
    ├── discover-repos/                 # Go tool to find subproject repos in CNCF orgs
    ├── generate-index/                 # Go tool to generate an index of SBOMs (with project hierarchy)
    ├── generate-tooling-sbom.sh        # Generates repo-tooling and CI-chain SBOMs for this repo
    ├── sbom-lib.sh                     # Shared folder-slug, parent-project, tag and import helpers
    ├── sbom-plan.sh                    # Plans a run: (repository, tag) pairs missing from the buckets
    ├── sbom-worker.sh                  # Processes one worker's share of the plan
    ├── postprocess-spdx.sh             # Shared SPDX post-processing applied before every upload
    ├── postprocess-cdx.sh              # Same for imported CycloneDX release assets
    ├── postprocess-spdx3.sh            # Same for imported SPDX 3 JSON-LD release assets
    ├── migrate-subproject-keys.sh      # Moves subproject SBOMs to the project slug (dry-run by default)
    ├── ingest-sbom-oci.sh              # Script to upload local SBOMs to OCI buckets
    ├── generate-sbom-local.sh          # Local testing script (Linux/macOS)
    └── generate-sbom-local.ps1         # Local testing script (Windows)

GitHub Actions Workflows

1. Sync CNCF Projects (sync-cncf-projects.yml)

Automatically syncs the list of CNCF projects from the official landscape.

  • Scheduled: Daily at 03:00 UTC
  • Manual trigger: Via workflow_dispatch
  • Output: util/data/cncf-projects.yaml

2. Discover Additional Repos (discover-cncf-repos.yml)

Scans GitHub organizations of CNCF projects to find additional subproject repositories with releases.

  • Scheduled: Weekly on Monday at 04:00 UTC
  • Manual trigger: Via workflow_dispatch
  • Output: util/data/discovered-repos.yaml

This workflow finds subproject repositories that:

  • Belong to the same GitHub org/user as a CNCF project
  • Have at least one release
  • Are not forks, archived, or disabled

3. Generate SBOMs (generate-sbom.yml)

Generates SBOMs for CNCF and manually added projects and uploads them directly to OCI S3 buckets.

  • Scheduled: Daily at 02:00 UTC (releases from the past 60 days that are not yet in the bucket; a failed run is caught up by the next one)
  • Manual trigger: Via workflow_dispatch with optional filters
Input Description Default
project_filter Filter by owner/repo (e.g., "coredns/coredns") empty (all projects)
force_regenerate Force regenerate existing SBOMs false
releases_mode recent (past 60 days) or latest (N latest releases) recent
max_releases Max releases per repo (only for latest mode) 3
source all, cncf, manual, or discovered all

How it works:

  1. plan: util/sbom-plan.sh reads cncf-projects.yaml, the manually maintained repositories.yaml and discovered-repos.yaml (2300+ repositories), fetches their releases and tags with GraphQL (50 repositories per query), lists both buckets once and writes the (repository, tag) pairs whose *_spdx.json object is missing (all pairs with force_regenerate) to a plan.jsonl artifact. Tags with a path segment (api/v0.21.3, kyaml/v0.21.3, pkg/app/.../v0.1.0) version a sub-component, not the repository, and would collide on the version-only key, so they are skipped
  2. generate: one matrix job per ~15 planned SBOMs (util/sbom-worker.sh, round-robin share of the plan, at most 30 in parallel). The matrix scales with the work, not with the repository count, so the 256-entry matrix limit is never hit and a quiet night costs a handful of jobs instead of 2300
  3. SBOMs the project attached to the GitHub release are imported (see Release-attached SBOMs): a project-wide SPDX 2.x asset replaces our own scan, a per-binary one is uploaded next to it as *_release_spdx.json
  4. When the release has no project-wide SPDX 2.x asset, Waybill generates one with waybill sbom scan --format spdx-2.3-json (deps.dev and ClearlyDefined enrichment inline)
  5. Every SPDX 2.x SBOM is post-processed with util/postprocess-spdx.sh, every CycloneDX asset with util/postprocess-cdx.sh, every SPDX 3 asset with util/postprocess-spdx3.sh (see SBOM post-processing)
  6. Each SBOM is uploaded right after it is built, so a failing repository costs only its own item; the worker reports it and continues
  7. Workers write a sbom-result-<n> artifact; summarize-sbom-run turns them into the run summary (planned, uploaded, failed items)
  8. No files are committed to the repository

Adding a project manually

Add an entry to util/data/repositories.yaml:

repositories:
  - owner: OmniTrustILM
    repo: core
    name: OmniTrust ILM

Entries are included in scheduled and manually triggered source=all runs. Use source=manual to scan only this list; cncf and discovered retain their existing scopes. Duplicate owner/repo entries in the CNCF and manual lists are scanned once (case-insensitive, with CNCF metadata taking precedence). Manual projects use the project bucket and the existing project-name/version key layout. The sync and discovery workflows do not overwrite this list.

For the first scan of an older release, run the generation workflow with source=manual, project_filter=OmniTrustILM/core, and releases_mode=latest. Scheduled runs keep the existing recent-release selection; adding an entry does not itself trigger a run or backfill older releases. The local Bash and PowerShell generators also read repositories.yaml.

4. Migrate SBOMs to OCI (migrate-sboms-to-oci.yml)

One-time / on-demand migration of any legacy SBOM files from the repository into OCI S3 buckets.

  • Manual trigger only: Via workflow_dispatch
  • Skips objects that already exist in the bucket (unless FORCE is set)
  • Uses util/ingest-sbom-oci.sh, which applies the same folder naming and post-processing as generate-sbom.yml

5. Generate Tooling SBOM (generate-tooling-sbom.yml)

Generates SBOMs for this repository's own tooling on every push, pull request, and manual run.

  • Triggers: push, pull_request, workflow_dispatch
  • Artifact: tooling-sbom-<git-sha>
  • Tracked files on main:
  • Files:
    • tooling-repo.spdx.json — Waybill-generated SBOM for this repository at the checked-out commit
    • tooling-ci.spdx.json — SPDX inventory of the GitHub Actions and downloaded tools involved in SBOM generation

This separates the repository source inventory from the CI/generation chain, so the SBOM production tooling is captured explicitly as part of the delivered artifact set. The workflow generates files in tooling-sbom-out/, uploads them as an artifact for each run, and refreshes the tracked tooling-sbom/ snapshots on pushes to main.

6. Watch Sandbox Applications (watch-sandbox-applications.yml)

Polls cncf/sandbox issues daily at 02:17 UTC and on manual dispatch. Cross-repository issue creation does not directly trigger this repository's workflows, so new applications are picked up on the next poll.

Issues must start with [Sandbox]. GitHub repository URLs are extracted only from the Project repo URL in scope of application field. Each repository in that field gets one SBOM per application issue; links elsewhere in the issue are not scanned. Malformed application fields are reported in the logs.

The first run includes existing open applications. A persistent start timestamp also allows subsequent polls to include newly created applications that have already been closed. Historical closed applications are excluded.

For each unprocessed repository, the workflow selects its latest stable release, falling back to the default branch if no stable release exists. The selected ref is resolved to a commit SHA before scanning; branch snapshots use that SHA as their version. The document name explicitly identifies the repository and scanned version, independent of the temporary checkout directory. No project build scripts are run.

SBOMs and processing state are stored in the dedicated sandbox-reviews bucket, separate from CNCF project SBOMs. The optional OCI_SANDBOX_BUCKET repository variable overrides this default. Application snapshots use the prefix:

sandbox-applications/<issue-number>/<owner>/<repo>.spdx.json

For example, application #525 for KubeRay produces sandbox-applications/525/ray-project/kuberay.spdx.json. The key remains stable even if new releases appear: a successfully uploaded SBOM marks that issue/repository as processed. Failed scans or uploads are retried on subsequent polls. Workflow runs are serialized, with up to five repository scans in parallel; more than 256 pending repositories are deferred to subsequent runs.

The timestamp is stored in the same sandbox bucket at sandbox-applications/.started-at (not an SBOM). Keep this state and the uploaded snapshots to preserve one-time processing. The S3 key needs list, read, and write access to this prefix; access errors stop discovery rather than being interpreted as empty state. The scan step receives no S3 credentials. Generated SBOMs and their resolved revision metadata are also retained as GitHub Actions artifacts for seven days.

Required GitHub Secrets & Variables

The following must be configured in the repository settings:

Secrets:

Secret Description
OCI_S3_ACCESS_KEY S3-compatible access key with PutObject permission for the configured project, subproject, and sandbox buckets
OCI_S3_SECRET_KEY S3-compatible secret key for OCI Object Storage

Variables:

Variable Description Example
OCI_S3_ENDPOINT S3-compatible endpoint URL https://axtwf1hkrwcy.compat.objectstorage.us-sanjose-1.oraclecloud.com
OCI_S3_REGION S3 region us-sanjose-1
OCI_PROJECT_BUCKET Bucket name for project SBOMs cncf-project-sboms
OCI_SUBPROJECT_BUCKET Bucket name for subproject SBOMs cncf-subproject-sboms
OCI_SANDBOX_BUCKET Optional sandbox application bucket override (default: sandbox-reviews) sandbox-reviews

Troubleshooting upload failures

Upload failures fail the generation job, including partially successful uploads. The logs show the target bucket/key, AWS CLI exit code and diagnostic output; the uploaded output counts only successful uploads.

On S3-compatible storage, an error response with an empty Message can cause AWS CLI to print argument of type 'NoneType' is not a container or iterable instead of the service error. This has been reproduced with HTTP 403 AccessDenied. Check the permissions and bucket policy for the S3 key stored in GitHub's OCI_S3_ACCESS_KEY / OCI_S3_SECRET_KEY secrets. Successful read access does not establish PutObject permission. Manually listed projects use OCI_PROJECT_BUCKET, the same bucket as CNCF projects.

Running the Workflow Manually

  1. Go to Actions tab in GitHub
  2. Select "Generate SBOM for CNCF Projects"
  3. Click "Run workflow"
  4. Optionally specify a project filter or change the releases mode

The run summary lists planned and uploaded SBOMs and every failed owner/repo@tag; the sbom-plan artifact holds the full plan of the run.

Utility Tools

extract-projects

Go tool that downloads the CNCF landscape and extracts all projects with status graduated, incubating, or sandbox.

cd util/extract-projects
go run . ../data/cncf-projects.yaml

discover-repos

Go tool that scans GitHub organizations of CNCF projects to find additional subproject repositories with releases.

cd util/discover-repos
go run . /path/to/cncf-automation

The tool will:

  • Read the list of CNCF projects from cncf-projects.yaml
  • Scan each unique GitHub organization/user
  • Find subproject repos that have releases and contain a supported package manifest
  • Record the owning project of every entry as parent_project / parent_repo (also for entries discovered earlier, resolved from discovered_by)
  • Output results to discovered-repos.yaml

To only (re)record the parent fields without scanning GitHub:

cd util/discover-repos
go run . -backfill-only ../..

generate-index

Go tool that writes sbom/index.json for a local sbom/ tree (as produced by generate-sbom-local.sh). Besides sboms and subproject_sboms, the index contains a projects list (name, slug, owner, repo, status) from cncf-projects.yaml and repositories.yaml, and every subproject entry carries parent_project and parent_slug, so consumers can group SBOMs without heuristics. parent_slug is the subproject's parent folder in the bucket; it equals the parent's projects[].slug whenever parent_project is known.

cd util/generate-index
go run . ../..

migrate-subproject-keys.sh

Moves existing subproject SBOMs from the legacy parent folder (the parent's repository name) to the parent's project slug, e.g. argo-cd/argo-workflows/… → argo/argo-workflows/… and aeraki/meta-protocol-proxy/… → aeraki-mesh/meta-protocol-proxy/….

Dry-run by default: it lists the subproject bucket (read-only) and prints the plan (MOVE, CONFLICT, AMBIGUOUS, UNKNOWN) plus a per-folder summary. Only --apply changes the bucket: each object is copied server-side, the copy is verified (size and ETag), and only then is the old key deleted. Keys whose target already exists, and legacy folders that cannot be attributed to exactly one project, are reported and left in place.

# Print the plan (default; credentials from the environment or .env.sbom)
./util/migrate-subproject-keys.sh

# Review a saved key listing offline
./util/migrate-subproject-keys.sh --listing keys.txt

# Perform the moves (maintainers only, after reviewing the plan)
./util/migrate-subproject-keys.sh --apply

ingest-sbom-oci.sh

Script to upload local SBOM files to OCI S3 buckets. Supports both OCI CLI and S3-compatible auth modes.

# Using S3 auth (recommended for CI)
./util/ingest-sbom-oci.sh \
  --auth-mode s3 \
  --s3-endpoint https://axtwf1hkrwcy.compat.objectstorage.us-sanjose-1.oraclecloud.com \
  --s3-access-key "$ACCESS_KEY" \
  --s3-secret-key "$SECRET_KEY"

# Dry run to preview uploads
./util/ingest-sbom-oci.sh --dry-run

# Force overwrite existing objects
./util/ingest-sbom-oci.sh --force

Credentials can also be provided via environment variables or a .env.sbom / .env file.

generate-tooling-sbom.sh

Generates the repository's own tooling SBOM plus a second SPDX document for the GitHub Actions and downloaded tools that form the SBOM generation chain.

# Full output (requires waybill in PATH)
./util/generate-tooling-sbom.sh

# Only regenerate the CI/generation-chain SBOM
./util/generate-tooling-sbom.sh --skip-repo-sbom

# Write directly into the tracked repo snapshot directory
./util/generate-tooling-sbom.sh --output-dir tooling-sbom

Local Testing

Prerequisites

  • git
  • GitHub CLI (gh)
  • jq (for bash script)
  • yq
  • Waybill (auto-downloaded by the script, or install manually)

Note: Go is not required for SBOM generation (Waybill is a precompiled Rust binary). If Go is installed, the local script writes sbom/index.json with util/generate-index.

Bash Script (Linux/macOS/WSL)

The script reads the same repository lists as the scheduled workflow (cncf-projects.yaml, repositories.yaml, discovered-repos.yaml) and shares its release selection, release-asset import, Waybill flags and post-processing via util/sbom-lib.sh. Unlike the workflow it skips releases that already exist as a local file, not in the bucket. A cached Waybill in .local/bin is replaced if it is not WAYBILL_VERSION.

# Process all repositories
./util/generate-sbom-local.sh

# Process specific repo
./util/generate-sbom-local.sh coredns/coredns

# One specific release, regenerated
./util/generate-sbom-local.sh --force --tag v0.10.0 kagent-dev/kagent

# Only discovered subprojects (written to sbom/subprojects/<owner>/<repo>/...)
./util/generate-sbom-local.sh --source discovered argoproj/argo-workflows

# Set max releases per repo (default: 3)
MAX_RELEASES=5 ./util/generate-sbom-local.sh

PowerShell Script (Windows)

A thin wrapper around the bash script; needs bash from Git for Windows or WSL.

# Process all projects
.\util\generate-sbom-local.ps1

# Process specific repo
.\util\generate-sbom-local.ps1 -ProjectFilter "coredns/coredns"

# Force regenerate
.\util\generate-sbom-local.ps1 -Force -ProjectFilter "coredns/coredns"

# Set max releases per repo
.\util\generate-sbom-local.ps1 -MaxReleases 5

Environment Variables

Variable Description
GH_TOKEN or GITHUB_TOKEN GitHub token for API access (recommended for higher rate limits)
MAX_RELEASES Maximum releases to process per repo (default: 3, bash only)
WAYBILL_VERSION Waybill release version to use (default: v0.10.0)

Tests

bash util/tests/sbom-lib.sh                 # folder slugs, release import, plan + worker end to end
bash util/tests/postprocess-spdx.sh         # SPDX post-processing
bash util/tests/postprocess-cdx.sh          # CycloneDX post-processing
bash util/tests/postprocess-spdx3.sh        # SPDX 3 post-processing
bash util/tests/migrate-subproject-keys.sh  # migration plan (stubbed aws, no bucket access)
bash util/tests/s3-upload.sh
bash util/tests/prepare-project-matrix.sh
bash util/tests/generate-sandbox-sbom.sh
python3 -m unittest discover -s util/tests -p test_sandbox_applications.py
(cd util/discover-repos && go test ./...)
(cd util/generate-index && go test ./...)

Project List

CNCF projects are automatically synced from the official CNCF Landscape.

Projects with the following status are included:

  • graduated
  • incubating
  • sandbox

Note: You do not need to manually add projects. The sync workflow runs daily and updates the project list automatically. If a project is missing, ensure it is properly listed in the CNCF Landscape with the correct project status.

Release Filtering

The generator only processes stable releases:

Included:

  • Full releases (e.g., v1.0.0, v2.5.3)
  • Releases marked as non-prerelease and non-draft in GitHub

Excluded:

  • Alpha releases (e.g., v1.0.0-alpha.1)
  • Beta releases (e.g., v1.0.0-beta.2)
  • Release candidates (e.g., v1.0.0-rc1)
  • Development versions (e.g., v1.0.0-dev)
  • Snapshots, nightly, canary builds
  • Draft releases

The pre-release check only matches at the end of the tag (v1.0.0-rc.1, v2-preview), so names like chart-test-tools-1.0.0 stay in. Repositories without GitHub releases fall back to their newest semver tags. At most the newest 100 releases of a repository are inspected.

SBOM Format

Generated SBOMs are in SPDX 2.3 JSON format, containing:

  • Package information with real dependency relationships (not flat fan-out)
  • SHA-256 content hashes on every component
  • License information enriched via deps.dev (licenseDeclared) and ClearlyDefined (licenseConcluded; opt-in flag --clearly-defined since Waybill 0.10)
  • Dependency relationships extracted from lockfiles (go.sum, Cargo.lock, package-lock.json, etc.)
  • All lifecycle scopes are kept; build/dev/test-only dependencies are flagged, not dropped: typed BUILD_DEPENDENCY_OF / DEV_DEPENDENCY_OF / TEST_DEPENDENCY_OF relationships plus a waybill:lifecycle-scope annotation on the package. Filter with --exclude-scope in Waybill or by relationship type downstream
  • Document-level quality annotations (waybill:graph-completeness, waybill:go-transitive-coverage, waybill:unresolved-declared-dep-count) say what the scanner could not determine
  • Per-component evidence blocks with confidence scoring

Example SBOM Metadata

Each SBOM includes:

  • SPDX 2.3 document information
  • Creator: Tool: waybill-0.10.0, plus the scanned source (source: git:https://github.com/<owner>/<repo>.git#<tag>)
  • Scope annotation (manifest vs artifact SBOM)
  • Per-component waybill:sbom-tier annotations (source, deployed, analyzed, etc.)

SBOM post-processing

Every SBOM is passed through util/postprocess-spdx.sh before it is uploaded, on every path that writes to a bucket (generate-sbom.yml via util/sbom-worker.sh, ingest-sbom-oci.sh/migrate-sboms-to-oci.yml, generate-sandbox-sbom.sh, and the local scripts). It uses only the repository, the release tag and the CNCF landscape project name, and guarantees:

Field Value Example
name <owner>/<repo> <tag> (never a temporary checkout name) kagent-dev/kagent v0.10.0
root package downloadLocation git+https://github.com/<owner>/<repo>.git@<tag> git+https://github.com/kagent-dev/kagent.git@v0.10.0
root package purl pkg:github/<owner>/<repo>@<tag> (owner/repo lowercased, tag percent-encoded); replaces Waybill's pkg:generic/<owner>%2F<repo> purl pkg:github/kagent-dev/kagent@v0.10.0
root package supplier Organization: <project>, for subprojects the parent project; NOASSERTION if no project is known, never the SBOM tool Organization: Argo
root package name / versionInfo <owner>/<repo> / <tag> (release assets otherwise name a tarball, binary or image here, e.g. flux_2.9.6_source_code.tar.gz / sha256:…) kagent-dev/kagent / v0.10.0

The root package is the one the document DESCRIBES. A project name containing parentheses gets an empty contact, e.g. Organization: Open Policy Agent (OPA) (), because SPDX tools otherwise read (OPA) as an e-mail address. documentNamespace is not changed and stays unique per document; everything else (packages, relationships, creators, annotations) is left as Waybill or the project's tool produced it. The script is idempotent and can also be run on already published documents (util/postprocess-spdx.sh --project <name> <file>; owner, repo and tag are then taken from the root package).

Release-attached SBOMs

Many projects ship SBOMs with their GitHub release. Those are imported instead of or in addition to our own Waybill scan. Consumers must key on the fields the post-processing guarantees (document name, root purl pkg:github/<owner>/<repo>@<tag>, root downloadLocation, root name/versionInfo, supplier) and on the bucket key — never on creationInfo.creators, documentNamespace or package counts, which differ by tool.

How assets are picked (sbom_import_release_assets in util/sbom-lib.sh):

  • Candidates are JSON assets up to 100 MB whose name contains spdx, cdx, cyclonedx or bom. At most 12 are downloaded, project-wide names first, then by size. The format is detected from the content, not the name: SPDX 2.x, SPDX 3 (@context with spdx.org/rdf/3), CycloneDX (bomFormat).
  • Project-wide asset (name without an OS/arch token such as linux, amd64, arm64, windows): stored as <slug>_<ver>_spdx.json / _spdx3.json / _cdx.json. For SPDX 2.x this replaces the Waybill scan. Examples: flux_2.9.6_sbom.spdx.json (syft over the source tarball), k6-v1.8.2-spdx.json, trivy's bom.json (CycloneDX).
  • Per-binary / per-image asset (e.g. cosign-linux-amd64_3.1.3_linux_amd64.sbom.json, otelcol-contrib_…_linux_arm64.tar.gz.sbom.json, kubectl-vela-…-linux-amd64.spdx.sbom.json): the largest one is stored as <slug>_<ver>_release_spdx.json (or _release_cdx.json, _release_spdx3.json) next to the Waybill scan, which still runs. Such SBOMs describe one build for one platform: runtime modules only, no build/test scopes, no source-tree view. They are useful as the project's own statement, not as the project SBOM.
  • Every imported SPDX 2.x document (_spdx.json and _release_spdx.json) goes through util/postprocess-spdx.sh, so the root package is <owner>/<repo> at <tag> with a pkg:github purl, git downloadLocation and the CNCF project as supplier. If the asset is not a valid SPDX 2.x document with a DESCRIBES root, it is dropped and Waybill generates instead.
  • Every imported CycloneDX document (_cdx.json, _release_cdx.json) goes through util/postprocess-cdx.sh: metadata.component.name/version become <owner>/<repo> / <tag>, a vcs external reference points at the repository and metadata.supplier is the CNCF project. The component purl is kept (CycloneDX allows one; the tool's pkg:golang/... carries ecosystem information). BOMHort derives the SBOM name from metadata.component, so imported CycloneDX shows up as <owner>/<repo> <tag> too.
  • Every imported SPDX 3 JSON-LD document (_spdx3.json, _release_spdx3.json) goes through util/postprocess-spdx3.sh: SpdxDocument.name becomes <owner>/<repo> <tag>, the root package(s) (rootElement, else describes targets) get name, software_packageVersion, git software_downloadLocation and a suppliedBy Organization agent. BOMHort reads SPDX 3 through protobom, which takes the display name from SpdxDocument.name.

Known differences and limits of imported SBOMs:

Topic Waybill scan Imported release SBOM
Licenses licenseDeclared from deps.dev, licenseConcluded from ClearlyDefined depends on the tool; syft sets licenseConcluded, usually no licenseDeclared
Scopes all scopes, flagged via *_DEPENDENCY_OF + waybill:lifecycle-scope usually runtime only, no scope flags
Quality annotations waybill:graph-completeness etc. none
CycloneDX assets – root component normalised (name, version, supplier, vcs reference); purl stays the tool's, e.g. trivy pkg:golang/github.com/aquasecurity/trivy@v0.76.0?goos=linux&goarch=amd64; no pkg:github purl
SPDX 3 assets – document name and root package(s) normalised (name, version, software_downloadLocation, suppliedBy); no purl added
Several components per release (kubevela: sbom-vela-cli + sbom-vela-core) one SBOM for the repo only the largest is kept
Tag-value SPDX (istio istio-release.spdx), tarballs (argo-cd sbom.tar.gz), XML CycloneDX – ignored, Waybill generates

Best practice for projects publishing their own SBOM

If your project attaches an SBOM to its GitHub release, this pipeline prefers it over its own scan. To make sure it is picked up and useful:

  1. Publish one project-wide SPDX 2.3 JSON document that describes the release as a whole (source tree or source tarball), e.g. <project>_<version>_sbom.spdx.json. Per-binary SBOMs (…_linux_amd64.sbom.json) are welcome in addition, but they are stored as *_release_spdx.json and do not replace the generated project SBOM.
  2. Name the asset so it is recognised: the name must contain spdx, cdx, cyclonedx or bom and end in .json. sbom.tar.gz, tag-value .spdx and XML are ignored. Keep the project-wide asset free of OS/arch tokens (linux, darwin, windows, amd64, arm64, x86_64, …) — that is how it is told apart from per-binary files.
  3. Keep it under 100 MB and make it valid JSON with a DESCRIBES root (documentDescribes or a SPDXRef-DOCUMENT DESCRIBES … relationship). Documents without a described root are dropped and the pipeline scans instead.
  4. SPDX 2.x or CycloneDX JSON, both are normalised and consumed. SPDX 2.x gets the fuller treatment (pkg:github purl, downloadLocation) and is the format our own scans use, so prefer it when you have the choice. SPDX 3 JSON-LD is accepted and normalised too.
  5. Include what the scanner cannot see: licenseDeclared as well as licenseConcluded, checksums per package, and — if your tool supports it — build/test dependencies typed as BUILD_DEPENDENCY_OF / TEST_DEPENDENCY_OF rather than dropped. A source-tree scan (syft dir:., waybill sbom scan --path .) gives a fuller picture than a scan of the shipped binary.
  6. One SBOM per release, not per component, or at least one that covers the whole repository. If a release contains several components (CLI, controller, images), only the largest project-wide document is imported.
  7. Attach it to the GitHub release of the tag, not only to a container registry or a separate -sbom release; the importer looks at releases/tags/<tag> only.

Good examples today: fluxcd/flux2 (flux_<version>_sbom.spdx.json, syft over the source tarball) and grafana/k6 (k6-<version>-spdx.json).

Troubleshooting

Rate Limiting

If you encounter GitHub API rate limits:

  1. Set the GH_TOKEN environment variable with a valid GitHub token
  2. Run gh auth login to authenticate the GitHub CLI

Waybill Installation

Waybill is a precompiled binary — no build step required:

# The local script auto-downloads Waybill if not found in PATH
./util/generate-sbom-local.sh

# Or install manually
curl -sL https://github.com/kusari-oss/waybill/releases/download/v0.10.0/waybill-v0.10.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
sudo cp waybill-v0.10.0-x86_64-unknown-linux-gnu/waybill /usr/local/bin/

Clone Failures

Some repositories may have protected tags or require authentication. The script will skip these and continue with other releases.

Enrichment Timeouts

deps.dev and ClearlyDefined enrichment runs inline. If network is slow, scans may take longer (2-5 minutes per repo is normal). Use --offline flag with Waybill to skip enrichment for faster but less complete SBOMs.

S3 Upload Issues

If uploads fail with MissingContentLength, ensure the AWS CLI version is up to date and the AWS_REQUEST_CHECKSUM_CALCULATION=when_required environment variable is set.

Contributing

  1. Fork the repository
  2. Make your changes
  3. Test locally with the provided scripts
  4. Submit a pull request

For issues with specific CNCF projects, please contact the respective project maintainers directly.

About

House of SBOMs (and tooling)

Resources

Code of conduct

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages