You can generate most kinds of bill of materials with a small set of official open source tools. cdxgen, an OWASP Foundation project, produces CycloneDX SBOMs, SaaSBOMs, OBOMs, CBOMs, HBOMs and AI-BOMs through dedicated commands. The CycloneDX CLI merges, converts and validates them, the SPDX tools handle SPDX documents, and OpenVEX vexctl writes VEX. MBOMs, most HBOM business fields and VDR analysis still need a human.
This guide walks through each BOM type with real commands, shows a minimal schema valid CycloneDX 1.7 document, and ends with a GitHub Actions workflow that pins every tool version. Every flag below comes from the tool's own README or docs as of 23 October 2026.
Key takeaways
- One generator covers six BOM types. cdxgen ships
cdxgen,obom,cbom,saasbom,hbomandaibomcommands, all emitting CycloneDX JSON, with CycloneDX 1.7 as the default spec version. - cdxgen moved. Since v13 (first published to npm on 13 August 2026) the package is
@cdxgen/cdxgenand the repository is github.com/cdxgen/cdxgen. The old@cyclonedx/cdxgenname stays on the 12.x line and still gets fixes, but no v13 features. - Ecosystem specific CycloneDX generators (npm, Python and others under the CycloneDX GitHub organisation) are a good choice when you want an SBOM straight from one package manager's lockfile.
- The CycloneDX CLI is the official tool for merge, diff, convert and schema validation. Its validator defaults to spec version 1.7 and can fail a build on errors.
- SPDX output is available two ways: cdxgen exports SPDX 3.0.1 JSON-LD, and the CycloneDX CLI converts to SPDX JSON 2.3. The SPDX project's own Java and Python tools validate and convert SPDX documents.
- VEX is a separate document. OpenVEX vexctl creates, merges, validates and attests VEX statements; it does not inventory components.
- Some BOM content cannot be scanned. Manufacturing process detail (MBOM), supplier and procurement fields in an HBOM, and the analysis inside a VDR all need people who know the product.
- Pin every tool by version and checksum in CI. BOM generators run with access to your source tree and build environment, so treat them like any other build dependency.
Which official tool generates which BOM type?
The short answer is that cdxgen generates the widest range of BOM types from one install, while the CycloneDX and SPDX organisations provide the tools that combine, convert and check what you generate. The table below maps each BOM type to the official tool path, the command to start with, and the spec version you get by default.
| BOM type | What it records | Official open source path | Starting command | Default output |
|---|---|---|---|---|
| SBOM (source or lockfile) | Libraries and their dependency graph | cdxgen, or a CycloneDX ecosystem generator such as cyclonedx-npm or cyclonedx-py | cdxgen -o bom.json . | CycloneDX 1.7 JSON (cdxgen); CycloneDX 1.6 JSON (cyclonedx-npm) |
| SBOM (container image) | OS packages and application packages in image layers | cdxgen with the docker project type | cdxgen -t docker alpine:3.20 -o image.json | CycloneDX 1.7 JSON |
| SaaSBOM | Services, endpoints, data flows and their classification | cdxgen saasbom command, evinse | saasbom -o saasbom.json . | CycloneDX 1.7 JSON |
| OBOM | Operating system, installed packages, runtime configuration of a live host | cdxgen obom command (alias for the os project type) | obom -o obom.json | CycloneDX 1.7 JSON |
| CBOM | Algorithms, keys, certificates and crypto libraries | cdxgen cbom command, tracebom for runtime tracing | cbom -t java -o cbom.json . | CycloneDX 1.7 JSON |
| HBOM | Physical hardware of the current host | cdxgen hbom command | hbom -o hbom.json | CycloneDX 1.7 JSON |
| AI-BOM or ML-BOM (repository) | Models, AI services, prompt and MCP configuration, agent frameworks | cdxgen aibom command | aibom . | CycloneDX 1.7 JSON, AI inventory in formulation |
| AI-BOM (single Hugging Face model) | Model card metadata, datasets, licence, completeness score | OWASP AIBOM Generator | python3 -m src.cli google-bert/bert-base-uncased | CycloneDX 1.6 JSON |
| VEX | Exploitability status of a vulnerability in a product | OpenVEX vexctl | vexctl create ... | OpenVEX JSON |
| VDR | Known vulnerabilities affecting a product, with analysis | cdxgen with OWASP dep-scan, plus human analysis | see the VDR section below | CycloneDX JSON |
| SPDX document | Packages, files, relationships, licences | cdxgen SPDX export; SPDX tools-java and tools-python for validation and conversion | spdxgen -o bom.spdx.json . | SPDX 3.0.1 JSON-LD |
Two notes on the table. First, it describes official paths, not a ranking. Several other open source and commercial generators exist; our earlier overview of SBOM generators covers the general category. Second, CycloneDX defines each of these BOM types on its capabilities pages (SaaSBOM, OBOM, CBOM, MBOM, ML-BOM, VDR and others), but the specification does not care which tool wrote the file. A CBOM is a CycloneDX document whose components include cryptographic-asset entries; a SaaSBOM is one whose services array carries the inventory. That is why one generator can produce all of them.
Install the tools once
Every walkthrough below assumes the tools are on your path. Pick one install route per machine and record the versions you used, because flags change between releases.
cdxgen
The cdxgen README recommends the standalone release binaries or the container image for CI and production, and the npm package when you already manage JavaScript tooling. As of this writing the latest npm release is 13.4.0, published 14 October 2026.
# Option 1: npm (needs Node.js 24 or later per the README)
npm install -g @cdxgen/cdxgen --ignore-scripts --min-release-age=2
# Option 2: Homebrew
brew install cdxgen
# Option 3: container image, no local Node.js needed
docker run --rm -v /tmp:/tmp -v "$(pwd)":/app:rw -t ghcr.io/cdxgen/cdxgen:master -r /app -o /app/bom.json
# Option 4: a standalone binary, verified against its published checksum
VERSION="v13.4.0"
ASSET="cdxgen-linux-amd64"
curl -fsSLO "https://github.com/cdxgen/cdxgen/releases/download/${VERSION}/${ASSET}"
curl -fsSLO "https://github.com/cdxgen/cdxgen/releases/download/${VERSION}/${ASSET}.sha256"
sha256sum -c "${ASSET}.sha256"
chmod +x "${ASSET}"
The npm package installs every command used in this post; release binaries exist for most of them individually. If you still run the 12.x line, some v13 commands and flags will not exist there.
CycloneDX CLI
# Homebrew tap maintained by the CycloneDX project
brew install cyclonedx/cyclonedx/cyclonedx-cli
# Or the release binary (v0.33.1 at the time of writing)
curl -fsSL -o cyclonedx \
https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.33.1/cyclonedx-linux-x64
chmod +x cyclonedx
# Or the container image
docker run --rm -v "$(pwd)":/work cyclonedx/cyclonedx-cli --help
SPDX tools and vexctl
# SPDX Python tools (PyPI package spdx-tools, 0.8.5 at the time of writing)
python -m pip install spdx-tools==0.8.5
# SPDX Java tools (v2.0.7): download tools-java-2.0.7-jar-with-dependencies.jar
# from https://github.com/spdx/tools-java/releases
# OpenVEX vexctl (v0.4.4 at the time of writing)
brew install vexctl
# or: go install github.com/openvex/vexctl@v0.4.4
Step by step: an SBOM from a lockfile
A lockfile SBOM is the most common deliverable and the easiest to get right, because the lockfile already contains resolved versions. You have two official routes.
Route A: cdxgen
cd my-service
# Whole repository, every ecosystem cdxgen detects
cdxgen -r -o bom.json .
# One ecosystem only, production dependencies only
cdxgen -t js --required-only -o bom.json .
# Ask cdxgen to resolve licences from public registries (slow, off by default)
export FETCH_LICENSE=true
cdxgen -t java -o bom.json .
The README explains that the required-only option keeps packages with the "required" scope, which most people call production dependencies. Licence lookups query registries such as npm, Maven Central and NuGet, so set a GITHUB_TOKEN in CI to avoid rate limits.
cdxgen writes CycloneDX 1.7 by default. You can target 1.6 with the spec version option. Versions 1.4 and 1.5 are no longer generation targets in v13; the README says to generate at 1.6 or higher and then downgrade with cdx-convert:
cdxgen -r -o bom.json --spec-version 1.6 .
cdx-convert -i bom.json --to 1.5 -o bom-1_5.json
The cdxgen README also lists 2.0 as an accepted generation target. CycloneDX's own specification overview still shows 1.7 as the current version, released 21 October 2025, so treat 2.0 output as experimental until the CycloneDX project publishes it.
Route B: the ecosystem generators
The CycloneDX organisation maintains per ecosystem generators. They read one package manager's view of the world and nothing else, which makes their output predictable.
# npm: needs node 20.18.0 or later and npm 9 or later
npx @cyclonedx/cyclonedx-npm --package-lock-only --omit dev \
--output-format JSON --output-file bom.npm.json
# Python: from a virtual environment, a requirements file, Pipenv or Poetry
python -m pip install cyclonedx-bom
cyclonedx-py environment --spec-version 1.6 --output-format JSON --output-file bom.py.json
cyclonedx-py requirements requirements.txt --output-file bom.req.json
Two details matter here. cyclonedx-npm accepts spec versions 1.2 through 1.6 and defaults to 1.6, so if you mix its output with cdxgen output you will be combining 1.6 and 1.7 documents; the merge step later handles that. And the package lock only mode reads package-lock.json or npm-shrinkwrap.json without looking at node_modules, which is what you want in CI before an install.
For depth on how cdxgen adds reachability and occurrence evidence to a lockfile SBOM, see our post on cdxgen evidence and reachability.
Step by step: an SBOM from a container image
An image SBOM answers a different question from a lockfile SBOM: what actually ended up in the artifact you ship, including the base image's operating system packages.
# Pull and scan an image by reference
cdxgen -t docker alpine:3.20 -o image.json
# Scan an image you saved as an OCI archive
podman save -q --format oci-archive -o /tmp/app.tar my-app:1.4.2
cdxgen /tmp/app.tar -t docker -o image.json
# Scan an unpacked root filesystem offline
cdxgen /tmp/remote_target -t rootfs -o rootfs.json
The README notes that with the packaged helpers installed, container and rootfs BOMs also record repository trust sources, keyring and CA store entries as cryptographic-asset components, and supplier and author fields for OS packages. It also counts executables and shared libraries that no OS package owns, under the metadata properties cdx:container:unpackagedExecutableCount and cdx:container:unpackagedSharedLibraryCount.
If you only want some component types, the component type option filters the output, for example to libraries and data. The README is explicit that this is a filter, not a switch that enables discovery, so filtering to a type the scan never produced gives you an empty list.
cdxgen -t docker alpine:3.20 --spec-version 1.6 \
--component-type library --component-type data -o image-libs.json
Step by step: a SaaSBOM from source
CycloneDX describes a SaaSBOM as an inventory of services, endpoints, data flows and data classifications. In the document, that inventory lives in the top level services array: each service can list its endpoints, whether it is authenticated, whether it crosses a trust boundary, and which data flows in or out with what classification.
cdxgen builds this inventory two ways.
- From deployment manifests. The README says cdxgen detects service names from docker-compose, Kubernetes and Skaffold YAML and writes them into
services. A plain repository scan does this. - From source code. The
saasbomcommand (or the evinse command after a normal scan) analyses source to find endpoints and service calls. The README lists Java, Python, JavaScript, TypeScript and PHP as supported for SaaSBOM.
# Dedicated command: turns on evidence collection and deep mode
saasbom -o saasbom.json .
# Equivalent two step flow: scan, then add evidence and services with evinse
cdxgen -t js -o bom.json .
evinse -i bom.json -o bom.evinse.json -l javascript .
# Add call stack evidence through inter-procedural data-flow slicing (slower)
evinse -i bom.json -o bom.evinse.json --with-data-flow .
In the cdxgen source, the saasbom command sets evidence collection and deep mode on, and logs a warning when it is not running inside the cdxgen container, because source analysis depends on a bundled analysis engine (atom) that is easiest to get right in the image. The README adds that the standalone saasbom binary now embeds a native build of that engine on the common Linux, macOS arm64 and Windows amd64 targets, so no JDK is needed there. On the remaining targets it still needs Java 23 or later.
Plan to edit the result. A generator can find that your code calls an HTTP client with a given base URL; it cannot know that the endpoint handles cardholder data or that it is owned by a partner. The classification and trust boundary fields are usually filled in by the team that owns the service.
Step by step: an OBOM from a host or an image
An operations BOM, in CycloneDX's words, is a full stack inventory of runtime environments, configurations and dependencies in staging or production. cdxgen's obom command is an alias for the os project type.
# Live host inventory (Linux, Windows or macOS)
obom -o obom.json
# Same, with deeper collection and the runtime audit rules applied
obom -o obom.json --deep --bom-audit --bom-audit-categories obom-runtime
# Fail CI if the runtime audit finds anything rated high or above
obom -o obom.json --bom-audit --bom-audit-categories obom-runtime --bom-audit-fail-severity high
According to the README, OBOM collection is powered by osquery, which is installed with the binary plugins, and uses platform specific default queries. Expect it to take several minutes and to produce thousands of components: operating system, device drivers, files and data. Some macOS tables need elevated privileges and Full Disk Access.
For an image, you have two choices. Scan the image from outside with the docker project type, as in the previous section, or run obom inside a container started from that image. The cdxgen OBOM lessons explain that both name OS packages with the same package URL, so findings and diffs carry over. The one difference they call out: the image scan also lists the source package of each binary package, tagged source, and the OBOM does not, so drop those before comparing.
cdxgen -t docker my-image:1.4.2 -o image.json
# Then run obom inside a container of my-image:1.4.2, writing host.json,
# and compare the purl sets of image.json and host.json
An OBOM collects hostnames, users, startup items and other operational detail. Treat the file as sensitive, store it with the same access controls as your host configuration, and think before you attach it to a ticket.
Step by step: a CBOM from source
A cryptography BOM lists algorithms, keys, certificates and protocols, and links them to the components that use them. CycloneDX added the cryptographic-asset component type and the cryptoProperties object for this in version 1.6, and cdxgen validates that the type is only requested at 1.6 or newer.
# Java: keystores and certificates
cbom -t java -o cbom.json .
# JavaScript or TypeScript: algorithm inventory derived from source
cdxgen --include-crypto -o cbom.json /absolute/path/to/js-project
# Runtime: trace a command and record negotiated TLS cipher suites,
# protocols and crypto libraries actually used
tracebom --cmd "npm test" --trace-crypto -o cbom-runtime.json
The README describes the JavaScript and TypeScript analysis as lightweight constant propagation through common node:crypto, WebCrypto and JWT call sites. The source code for the cbom command shows it switches on crypto inclusion, evidence and deep mode, refuses the os project type (use obom for OS inventories), and does not accept the component type filter.
Be realistic about coverage. Static analysis finds algorithms named in code it can follow. It will miss algorithms chosen in configuration files read at runtime, crypto inside native libraries, and anything negotiated by a TLS terminator in front of the service. tracebom closes some of that gap for what a test run actually exercises. A CBOM built for post-quantum migration planning usually combines static output, runtime traces and a manual review of key management.
Step by step: an HBOM from a host
Our step by step HBOM guide covers how to author a hardware BOM in CycloneDX by hand. This section only covers the generated part.
cdxgen's hbom command inventories the hardware of the machine it runs on. The HBOM guide lists supported collectors for Apple Silicon macOS, Linux amd64 and arm64, and Windows amd64 and arm64. Each collector only works on its own operating system, so you run it on the device you want to describe.
# See what would run, without running hardware probes
hbom --dry-run
# Generate the hardware inventory
hbom -o hbom.json
# Check which native utilities or permissions were missing
hbom diagnostics --input hbom.json
# One host document that joins hardware with the OBOM runtime view
hbom --include-runtime -o host-view.json
Three details from the HBOM guide are worth knowing before you run it in production. HBOM generation targets CycloneDX 1.7. The collector redacts sensitive identifiers such as serial numbers and MAC addresses by default, and only includes raw values when you pass the sensitive option. And the hardware type must not be mixed with software project types in the same invocation; use the include runtime option when you want both.
What this cannot give you is the procurement side of an HBOM: the authorised supplier, the manufacturer part number as purchased, country of origin, end of life dates or counterfeit risk notes. Those come from purchasing records and supplier declarations, which is why most HBOM fields in practice are still authored by hand.
Step by step: an AI-BOM from a repository
An AI-BOM (CycloneDX calls the capability ML-BOM) records models, datasets and the AI services an application depends on. There are two official open source routes, and they answer different questions.
cdxgen aibom: what AI does this repository use?
# Dedicated command with AI defaults
aibom .
# Same inventory through the main CLI, with AI audit rules applied
cdxgen -r --include-formulation -o aibom.json --bom-audit --bom-audit-categories ai-bom .
# Direct AI-BOM for one model reference or local model file
aibom pkg:huggingface/google-bert/bert-base-uncased
aibom /absolute/path/to/model.gguf
# Re-audit a saved AI-BOM later, governance rules only
cdx-audit --bom aibom.json --direct-bom-audit --categories ai-governance
The cdxgen AI-BOM guide lists what it inventories: AI inference services found in source or configuration, model components and local model files (GGUF, safetensors, ONNX and PyTorch pickle checkpoints), agent frameworks and SDKs, prompt and model routing configuration, agent instruction and skill files, and MCP configuration, MCP Registry server.json manifests and MCPB bundles. With formulation enabled, that AI and agent inventory goes into the standard CycloneDX formulation section. Hugging Face models get pkg:huggingface package URLs with a revision where one is available.
OWASP AIBOM Generator: what is inside this one model?
The OWASP GenAI Security Project's AIBOM Generator starts from a Hugging Face model and extracts metadata from its model card, configuration and repository files. Its README says it emits CycloneDX 1.6 JSON, is built on the official CycloneDX Python library, and scores the completeness of the result with suggestions for missing fields.
git clone https://github.com/GenAI-Security-Project/aibom-generator
cd aibom-generator
pip install -r requirements.txt
python3 -m src.cli google-bert/bert-base-uncased \
--name "Support bot base model" --version "1.0.0" \
--manufacturer "Example Corp" --output model-aibom.json
One caution from the README: the inference option sends metadata to an AI service for enrichment and needs an API key. Leave it off if your model identifiers or descriptions are confidential.
The two tools combine well. cdxgen tells you which models a repository references; the OWASP generator gives you a richer description of each public model. Link them with the model's package URL so the model component in the repository AI-BOM points at the same identity as the model AI-BOM.
A minimal, schema valid CycloneDX 1.7 example
The document below is the smallest useful example we could write that shows three BOM types in one file: an SBOM component, a CBOM cryptographic asset and a SaaSBOM service. We validated it against bom-1.7.schema.json from the CycloneDX specification repository (with its referenced SPDX licence, JSF and cryptography definition schemas) using a JSON Schema draft 7 validator. Changing the algorithm primitive or the data flow direction to a value the schema does not list makes validation fail, which is a quick way to confirm your validator is actually loading the referenced schemas.
{
"$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2026-10-23T09:00:00Z",
"lifecycles": [{ "phase": "build" }],
"component": {
"type": "application",
"bom-ref": "orders-api",
"name": "orders-api",
"version": "2.4.0"
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:npm/express@4.21.2",
"name": "express",
"version": "4.21.2",
"purl": "pkg:npm/express@4.21.2",
"licenses": [{ "license": { "id": "MIT" } }]
},
{
"type": "cryptographic-asset",
"bom-ref": "crypto/algorithm/aes-256-gcm",
"name": "AES-256-GCM",
"cryptoProperties": {
"assetType": "algorithm",
"algorithmProperties": {
"primitive": "ae",
"parameterSetIdentifier": "256",
"mode": "gcm",
"cryptoFunctions": ["encrypt", "decrypt"],
"classicalSecurityLevel": 128,
"nistQuantumSecurityLevel": 1
},
"oid": "2.16.840.1.101.3.4.1.46"
}
}
],
"services": [
{
"bom-ref": "payments-gateway",
"name": "payments-gateway",
"endpoints": ["https://payments.example.com/v1/charge"],
"authenticated": true,
"x-trust-boundary": true,
"data": [{ "flow": "outbound", "classification": "PCI" }]
}
],
"dependencies": [
{
"ref": "orders-api",
"dependsOn": ["pkg:npm/express@4.21.2", "payments-gateway"]
},
{ "ref": "pkg:npm/express@4.21.2", "dependsOn": [] }
]
}
A few things to notice. The metadata.component is the subject of the BOM, which the CycloneDX CLI needs for a hierarchical merge. The lifecycles entry says this is a build time BOM, which matters when a consumer compares it with a deployed or runtime BOM. The dependencies array is what turns a flat list into a graph: the application depends on a library and on a service. And bom-ref values are the keys every other part of the document, including a separate VEX file, uses to point at a component.
To validate the same file yourself:
cyclonedx validate --input-file bom.json --input-version v1_7 --fail-on-errors
# or with cdxgen's validator, which adds deeper checks and benchmarks
cdx-validate -i bom.json
How do you merge and validate the outputs?
Once you have several BOMs, you have to decide whether to keep them separate and linked, or merge them into one file. The CycloneDX CLI handles the merge; the decision is yours.
Flat merge
A flat merge concatenates components, services and dependencies into one document. It is the right choice when the inputs describe parts of the same deliverable, such as the npm and Python SBOMs of one service.
cyclonedx merge \
--input-files bom.npm.json bom.py.json \
--output-file bom.merged.json \
--output-format json --output-version v1_7
Hierarchical merge
A hierarchical merge nests each input under a new top level component, so you can still tell which sub-BOM a component came from. The CLI README notes that every input needs its subject described in metadata.component, and that the name and version options are required.
cyclonedx merge --hierarchical \
--name orders-platform --version 2026.10.0 \
--input-files bom.json saasbom.json cbom.json \
--output-file platform.json --output-format json --output-version v1_7
Validate after every merge
Validation should run on every generated and merged file, not just the final one, so a broken input is caught where it was produced.
for f in bom.json saasbom.json cbom.json platform.json; do
cyclonedx validate --input-file "$f" --input-version v1_7 --fail-on-errors || exit 1
done
# Deeper checks and benchmarks from cdxgen
cdx-validate -i platform.json --benchmark scvs-l2,cra
cdx-validate runs the JSON schema check and then rule based benchmarks. Its docs list OWASP SCVS (87 controls across levels 1 to 3) and a set of 8 EU Cyber Resilience Act SBOM expectations, and it can emit SARIF for code scanning. By default it exits with code 3 when a failing finding is rated high or above. The CRA benchmark is cdxgen's interpretation of the regulation, not an official conformity check, so use it as a hygiene signal.
When not to merge
Keep BOMs separate when they have different audiences or different sensitivity. An OBOM with host users and startup items, or an HBOM with raw serial numbers, should not end up inside the SBOM you send to a customer. Separate documents that reference each other by serial number and bom-ref (CycloneDX calls this BOM-Link) are easier to share selectively. After any merge, check that the sections you care about survived:
jq '[.components[]? | .type] | group_by(.) | map({type: .[0], count: length})' platform.json
jq '.services | length' platform.json
For more validation patterns, see our post on validating SBOM files from the command line.
How do you produce SPDX output?
You have three supported routes, and they produce different SPDX versions.
| Starting point | Tool and command | SPDX version written | Notes from the tool docs |
|---|---|---|---|
| A fresh scan | spdxgen -o bom.spdx.json . | SPDX 3.0.1 JSON-LD | spdxgen is the alias for cdxgen with its format option set to spdx; set the option to cyclonedx,spdx to get both formats from one scan |
| An existing CycloneDX 1.6 or 1.7 JSON | cdx-convert -i bom.json -o bom.spdx.json | SPDX 3.0.1 JSON-LD | Validates output by default; other input spec versions fail on the SPDX path |
| An existing CycloneDX file of any supported version | CycloneDX CLI convert command with output format spdxjson (example below) | SPDX JSON 2.3 | The CLI README warns that SPDX and CycloneDX conversion can lose information |
| An SPDX 2.x document | java -jar tools-java-2.0.7-jar-with-dependencies.jar Convert hello.spdx hello.spdx.json TAG JSONLD | SPDX 3.0.1 JSON-LD | tools-java supports SPDX 2.0 to 2.3 and 3.0.1 |
| Any SPDX 2.2 or 2.3 file | pyspdxtools -i bom.spdx.json | Validates; converts between Tag/Value, RDF, JSON, YAML and XML | tools-python 0.8 writes SPDX 3.0 experimentally but cannot read it |
Choose the route by what your consumer accepts. Many procurement and regulatory workflows still expect SPDX 2.3 JSON, while newer SPDX 3 consumers want JSON-LD with profiles. Always validate the converted file with the SPDX tools before you send it:
# CycloneDX to SPDX JSON 2.3 with the CycloneDX CLI
cyclonedx convert --input-file bom.json --output-file bom.spdx.json --output-format spdxjson
# SPDX 2.3 JSON
pyspdxtools -i bom.spdx.json
# SPDX 2.x or 3.0.1 with the Java tools
java -jar tools-java-2.0.7-jar-with-dependencies.jar Verify bom.spdx.json
Conversion loses detail in both directions. CycloneDX services, cryptographic properties and formulation have no direct SPDX 2.3 equivalent, and SPDX file level relationships and snippet data have no direct CycloneDX equivalent. If you need both formats, generate both from the source of truth rather than converting a converted file again.
How do you produce VEX alongside the BOMs?
VEX is not a BOM. It is a statement about whether a known vulnerability affects a product. OpenVEX vexctl is the reference tool for the OpenVEX format; it creates documents, merges them, validates them and wraps them in signed attestations.
vexctl create \
--product="pkg:oci/orders-api@sha256:4f3c...?repository_url=ghcr.io/example" \
--vuln="CVE-2024-0000" \
--status="not_affected" \
--justification="vulnerable_code_not_in_execute_path" \
> orders-api.vex.json
# Combine statements from several teams about the same product
vexctl merge --product="pkg:oci/orders-api@sha256:4f3c...?repository_url=ghcr.io/example" \
orders-api.vex.json platform-team.vex.json > merged.vex.json
vexctl validate merged.vex.json
The product identifier and CVE above are placeholders. The status comes from a fixed list (not affected, affected, fixed, under investigation), and a not affected status needs a justification or impact statement, which vexctl validate checks. Use the same package URL your SBOM uses for the product so a consumer can join the two documents.
Which BOM types still need manual authoring?
Generators are good at inventory they can observe. They are poor at intent, process and accountability. This table summarises where people still have to write things.
| BOM type | What a tool can fill in | What people still have to author |
|---|---|---|
| SBOM | Components, versions, hashes, dependency graph, licences from registries | Supplier names where registries are silent, the BOM author and contact, known unknowns |
| SaaSBOM | Service names from manifests, endpoints and calls found in code | Data classification, trust boundaries, service ownership, third party provider details |
| OBOM | OS, packages, configuration snapshots from osquery | Which hosts are in scope, environment labels, change ownership |
| CBOM | Algorithms in followed code paths, keystores, certificates, traced TLS use | Key management practice, crypto in configuration and native code, migration priority |
| HBOM | Detected devices, firmware versions, buses, storage on the current host | Supplier, purchased part numbers, country of origin, lifecycle and counterfeit risk |
| AI-BOM | Model references, model files, frameworks, MCP and prompt files, model card fields | Intended use, training data provenance not on the card, evaluation results, approvals |
| MBOM | Formulation basics: cdxgen's include formulation option records git metadata and build tools | Declared versus observed manufacturing or build process, workflow steps, tasks and their inputs |
| VDR | Vulnerability matches for components, through cdxgen with OWASP dep-scan | Analysis state, impact, response and the completeness statement for the report |
| VEX | Document structure, merging, validation, signing (vexctl) | The decision itself: affected or not, and why |
The MBOM row deserves a note. CycloneDX describes the MBOM as declared and observed formulation for reproducibility. cdxgen can start the formulation section from your repository and build tools, but nothing scans a manufacturing line or a release process that lives in people's heads. Write it once, keep it under version control, and generate the observed half from your CI system's records where you can.
The VDR row is similar. The cdxgen README says it can automate VDR generation at scale with OWASP dep-scan. That gets you the matched vulnerabilities. CycloneDX positions VDR as a report that communicates known and unknown vulnerabilities and the completeness of that knowledge, and only your security team can state what was analysed and what was not.
How do you run all of this in GitHub Actions with pinned versions?
The workflow below generates an SBOM, a CBOM and an AI-BOM from the repository, validates each one, merges them hierarchically, exports SPDX, and uploads everything as a build artifact. It pins third party actions to full commit SHAs, pins cdxgen and the CycloneDX CLI to release tags, and checks the cdxgen binary against its published SHA-256 file. For a broader treatment of SBOMs in Actions, see our GitHub Actions SBOM guide.
name: boms
on:
push:
branches: [main]
release:
types: [published]
permissions:
contents: read
env:
CDXGEN_VERSION: v13.4.0
CDX_CLI_VERSION: v0.33.1
jobs:
generate:
runs-on: ubuntu-24.04
steps:
- name: Check out
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Download pinned cdxgen binaries and verify checksums
env:
GH_TOKEN: ${{ github.token }}
run: |
for a in cdxgen cbom aibom cdx-convert cdx-validate; do
gh release download "$CDXGEN_VERSION" --repo cdxgen/cdxgen \
--pattern "${a}-linux-amd64" --pattern "${a}-linux-amd64.sha256"
sha256sum -c "${a}-linux-amd64.sha256"
chmod +x "${a}-linux-amd64"
sudo mv "${a}-linux-amd64" "/usr/local/bin/${a}"
done
- name: Download pinned CycloneDX CLI
run: |
curl -fsSL -o cyclonedx \
"https://github.com/CycloneDX/cyclonedx-cli/releases/download/${CDX_CLI_VERSION}/cyclonedx-linux-x64"
echo "${CDX_CLI_SHA256} cyclonedx" | sha256sum -c
chmod +x cyclonedx && sudo mv cyclonedx /usr/local/bin/cyclonedx
env:
CDX_CLI_SHA256: ${{ vars.CDX_CLI_SHA256 }}
- name: Generate SBOM, CBOM and AI-BOM
env:
FETCH_LICENSE: "true"
GITHUB_TOKEN: ${{ github.token }}
run: |
mkdir -p boms
cdxgen -r --required-only -o boms/sbom.json .
cbom -t java -o boms/cbom.json .
aibom -o boms/aibom.json .
- name: Validate every BOM
run: |
for f in boms/*.json; do
cyclonedx validate --input-file "$f" --input-version v1_7 --fail-on-errors
done
cdx-validate -i boms/sbom.json --benchmark scvs-l2
- name: Merge and export SPDX
run: |
cyclonedx merge --hierarchical \
--name "${{ github.event.repository.name }}" --version "${{ github.sha }}" \
--input-files boms/sbom.json boms/cbom.json boms/aibom.json \
--output-file boms/merged.json --output-format json --output-version v1_7
cyclonedx validate --input-file boms/merged.json --input-version v1_7 --fail-on-errors
cdx-convert -i boms/sbom.json -o boms/sbom.spdx.json
- name: Upload BOMs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: boms-${{ github.sha }}
path: boms/
retention-days: 90
Adapt it before you use it:
- Record the CycloneDX CLI checksum yourself. The CLI release page we checked did not publish a checksum file next to the Linux binary, so compute the SHA-256 once on a trusted machine and store it as a repository variable, as the workflow does.
- Drop the steps you do not need. The
cbom -t javaline assumes a Java project; usecdxgenwith its include crypto option for JavaScript and TypeScript. Theaibomline only makes sense if the repository uses AI services or models. - Do not run
obomorhbomhere. A hosted runner is a throwaway virtual machine; its OBOM or HBOM describes the runner, not your product. Run those commands on the hosts and images you actually operate. - Leave dependency installation off where you can. The
cdxgen-securevariant always disables package installation. Its own warning says secure mode limits what cdxgen does but does not make it safe to run against malicious code, so scan untrusted repositories in an isolated job. - Run SaaSBOM generation in the cdxgen container. If you add
saasbom, use the container image as the job container, because its source analysis is most reliable there. - Sign what you publish. cdxgen ships
cdx-signandcdx-verifyfor JSON Signature Format signatures inside the BOM, and you can also sign the file as a blob with Sigstore if your consumers verify that way.
What does Safeguard generate, and what does it not?
Safeguard's direct fit for this topic is the SBOM row, and an early access AI inventory. For the other BOM types, the honest answer is that you should use the open source tools above, and Safeguard can store and analyse the SBOMs that result.
What Safeguard does:
- SBOM generation and analysis, generally available. Safeguard generates SBOMs in SPDX (JSON, XML and RDF) and CycloneDX (JSON and XML), with more than 30 export formats including CSV, Excel and PDF. A Deep Analysis option uses Safeguard's Griffin models. Scanning runs continuously on push, on a schedule or on release. From the Safeguard CLI you can scan a directory, a container image or an existing SBOM file, and results land as JSON under
./output(commands below the list). Our post on how Safeguard generates and imports SBOMs shows the flow. - Importing SBOMs you generated elsewhere. An SBOM produced by cdxgen or a CycloneDX ecosystem generator can be scanned like any other SBOM file, so the platform shows which projects contain a given component and version.
- CSAF and VEX upload, generally available. You can upload CSAF and VEX documents to the platform.
- Licence compliance, generally available. SPDX expression parsing, licence policy, and NOTICE and attribution file generation.
- Attestation and signing, generally available. SLSA, Sigstore keyless signing through Fulcio and Rekor, in-toto and X.509, with Kubernetes admission control.
- Supplier SBOMs through TPRM, generally available. The Third Party Risk Manager lets you onboard vendors and request SBOMs, projects or products from them, then score and monitor supplier risk.
- AI-BOM discovery, early access. Safeguard inventories AI assets across code (SDK imports), cloud AI services, SaaS use and vector databases, and expresses them as a CycloneDX ML-BOM and in the SPDX AI profile. This is early access, so confirm coverage for your stack before you plan around it.
The Safeguard CLI commands for those three inputs:
safeguard scan --dir .
safeguard scan --image my-app:1.4.2
safeguard scan --sbom bom.json
What Safeguard does not do: generate HBOMs, CBOMs or cryptographic inventories, OBOMs, MBOMs, KBOMs, SaaSBOMs or dataset BOMs; author VDRs or bills of vulnerabilities; or generate SWID or CoSWID tags. It also does not convert between BOM formats beyond the export formats listed above. For those, use cdxgen, the CycloneDX CLI and the SPDX tools as described in this guide.
Common mistakes
- Pinning to an old package name. Installing
@cyclonedx/cdxgentoday gives you the 12.x line. That is fine if you chose it, but the v13 commands in this guide, such ascdx-convertwith SPDX 3.0.1 output, will not match what you run. - Generating with one spec version and validating against another. cdxgen defaults to CycloneDX 1.7, cyclonedx-npm defaults to 1.6, and the OWASP AIBOM Generator writes 1.6. Validate each file against the version it declares in
specVersion, then merge to a single output version. - Treating a filter as discovery. The component type option in cdxgen only filters. Asking for
machine-learning-modelcomponents without running an AI-BOM scan gives you nothing. - Running host BOMs on CI runners. An OBOM or HBOM from a hosted runner describes a temporary virtual machine. It is valid, and useless.
- Shipping raw identifiers by accident. The HBOM collector redacts serial numbers and MAC addresses unless you ask otherwise, but an OBOM still contains users, hostnames and startup items. Review before sharing.
- Assuming static CBOM output is complete. Algorithms selected in configuration, inside native libraries or at a TLS terminator are invisible to source analysis. Combine static output with runtime traces and a manual review.
- Converting a converted file. Each conversion between SPDX and CycloneDX can drop fields. Generate each format from the source scan instead of round tripping.
- Not pinning generator versions. Different generator versions find different components. Without a pinned version you cannot tell whether a change in your BOM came from your code or from the tool.
- Writing VEX that does not match the SBOM. A VEX statement whose product identifier is not the same package URL or
bom-refused in your SBOM cannot be joined automatically by the consumer.
FAQ
Can one tool generate every type of BOM?
Close to it. cdxgen generates SBOMs, SaaSBOMs, OBOMs, CBOMs, HBOMs and AI-BOMs as CycloneDX JSON through dedicated commands, and exports SPDX 3.0.1. It does not write VEX documents in OpenVEX format (use vexctl), and MBOM and VDR content still needs human input.
Is cdxgen still a CycloneDX project?
cdxgen describes itself as an OWASP Foundation production project, like CycloneDX itself. Since v13 its repository is github.com/cdxgen/cdxgen (the old CycloneDX organisation URL redirects there) and its npm package is @cdxgen/cdxgen.
What is the difference between cdxgen and the CycloneDX CLI?
cdxgen generates BOMs from source, images, hosts and models. The CycloneDX CLI works on BOMs that already exist: it adds files, analyses, diffs, merges, converts, signs, validates and verifies them. Most pipelines use both.
How do I generate an SBOM from package-lock.json only?
Run cyclonedx-npm in its package lock only mode, which reads the lockfile without looking at node_modules:
npx @cyclonedx/cyclonedx-npm --package-lock-only --output-file bom.json
cdxgen also reads lockfiles; run it before installing dependencies if you want lockfile only results.
Which CycloneDX version should my generator emit?
Emit the newest version your consumers can read. CycloneDX 1.7 is the current version per cyclonedx.org. If a consumer only reads 1.5, generate at 1.6 or 1.7 and downgrade with cdx-convert, setting its target version to 1.5. It reports the fields the older version does not define.
How do I make an SBOM in SPDX format with open source tools?
Run spdxgen (cdxgen with its format option set to spdx) for SPDX 3.0.1 JSON-LD, or convert a CycloneDX file with the CycloneDX CLI for SPDX JSON 2.3. Validate the result with the SPDX project's tools-java Verify command or pyspdxtools.
Can I generate an HBOM automatically?
Partly. cdxgen's hbom command inventories the hardware of the host it runs on (macOS on Apple Silicon, Linux and Windows on amd64 and arm64). Supplier, part number, origin and lifecycle fields usually come from procurement records and have to be added by hand.
How do I create an AI bill of materials for a Hugging Face model?
Use the OWASP AIBOM Generator with the model ID, for example python3 -m src.cli google-bert/bert-base-uncased, which writes CycloneDX 1.6 JSON and scores completeness. cdxgen's aibom command also accepts pkg:huggingface references and local GGUF files.
Does Safeguard generate CBOMs or HBOMs?
No. Safeguard generates and analyses SBOMs in SPDX and CycloneDX (generally available) and discovers AI assets as an ML-BOM (early access). Use cdxgen for CBOM, HBOM, OBOM and SaaSBOM generation.
Fix it without waiting for upstream
A BOM of any type only tells you what is inside a product. When it lists a vulnerable library or base image, someone still has to ship the fixed build, and that part of the job is the same whichever BOM type surfaced the problem.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
These demos show Safeguard working on SBOM style dependency data, not generating the other BOM types. They are most useful once you have SBOMs from the tools above and want to act on what they list.
This first demo is a real Safeguard CLI session. You ask it to fix a lodash CVE in a repository, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, and runs the install and tests with your approval. The same flow applies when a component in a cdxgen SBOM turns out to be vulnerable: the BOM finds it, the CLI helps you ship the fixed build.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with a device code, and runs a scan from the terminal. If your BOM generation has to happen on hosts you control, for example to keep source code or OBOM data off shared infrastructure, this is how scanning runs locally too.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once your SBOMs are in Safeguard, this is a way to ask questions such as which projects contain a given component version without leaving your assistant.
Click through it at your own pace, or open it full screen.
To try it on your own SBOMs, download Safeguard and follow the CLI docs.
Quick check
Which cdxgen command should you use to inventory the operating system and packages of a running host, and why not run it in CI?
Use obom, which is an alias for the os project type and collects through osquery. On a hosted CI runner it would describe the temporary runner, not the hosts you operate, so run it on your real hosts or inside a container started from your image.
You have a CycloneDX 1.7 SBOM from cdxgen and a consumer that only accepts SPDX 2.3 JSON. Which official tool converts it?
The CycloneDX CLI convert command, with the output format set to spdxjson, writes SPDX JSON 2.3. cdxgen's cdx-convert writes SPDX 3.0.1 JSON-LD instead. Validate the result with the SPDX tools, and expect some CycloneDX only fields, such as services and crypto properties, to be lost.
Sources
- cdxgen README (OWASP cdxgen repository)
- cdxgen HBOM guide
- cdxgen OBOM lessons
- cdxgen AI-BOM guide
- cdxgen cdx-convert guide
- cdxgen cdx-validate guide
- cdxgen project types reference
- cdxgen advanced usage: evinse and SaaSBOM
- cdxgen CLI option handling for cbom and saasbom (source)
- @cdxgen/cdxgen on the npm registry
- CycloneDX CLI README
- CycloneDX specification repository and JSON schemas
- CycloneDX specification overview
- CycloneDX capabilities: SaaSBOM
- CycloneDX capabilities: OBOM
- CycloneDX capabilities: CBOM
- CycloneDX capabilities: MBOM
- CycloneDX capabilities: ML-BOM
- CycloneDX capabilities: VDR
- CycloneDX cyclonedx-node-npm README
- CycloneDX cyclonedx-python README and usage docs
- SPDX tools-python README
- SPDX tools-java README
- OpenVEX vexctl README
- OWASP GenAI Security Project: AIBOM initiative
- OWASP AIBOM Generator resource page
- OWASP AIBOM Generator repository
- CISA Software Bill of Materials
Last reviewed: 23 October 2026. Tool flags and defaults change between releases; check each project's README before you pin a version.