Skip to main content

SPDX SBOM Output

CVE Lite CLI can write an SPDX 2.3 Software Bill of Materials in JSON. SPDX is the format named in US Executive Order 14028 and the one most federal and regulated buyers ask for by name, alongside CycloneDX.

Both SBOM formats come from the same scan, so a single command gives you the inventory and the vulnerability picture together.

Generating an SPDX SBOM​

cve-lite . --sbom spdx

This writes a timestamped file (cve-lite-scan-<timestamp>.spdx.json) to the current directory. Terminal output renders as normal.

--sbom accepts cyclonedx, spdx, and spdx2.3. Plain spdx means SPDX 2.3 today and will keep meaning 2.3, so a pinned CI command will not change format under you.

cve-lite . --sbom cyclonedx # CycloneDX 1.6
cve-lite . --cdx # the same thing, original flag, still supported

What the document contains​

Every package from the scanned lockfile appears in packages, not just the vulnerable ones, so the document is a useful compliance artifact even on a clean scan.

SPDX fieldValue
packages[].name / versionInfoPackage name and resolved version
packages[].downloadLocationResolved tarball URL, or NOASSERTION
packages[].licenseDeclaredDeclared license, or NOASSERTION
packages[].licenseConcludedAlways NOASSERTION
packages[].externalRefs (PACKAGE-MANAGER)pkg:npm/<name>@<version>
packages[].externalRefs (SECURITY)One advisory reference per advisory, linking to OSV
packages[].annotationsSeverity, advisory count, and the fix command
packages[].supplierAlways NOASSERTION
packages[].filesAnalyzedAlways false
relationshipsDESCRIBES for the root project, DEPENDENCY_OF for each dependency edge

filesAnalyzed is false because the scan reads lockfile metadata and never opens package contents. Omitting the field would default it to true and claim otherwise.

supplier is NOASSERTION because a lockfile does not record who published a package. Guessing a supplier from the package name would be a fabrication, and SPDX provides NOASSERTION precisely for this case.

Dependency relationships and NTIA minimum elements​

The NTIA minimum elements for an SBOM, which Executive Order 14028 points to, name seven required data fields: supplier name, component name, version, other unique identifiers, dependency relationship, author, and timestamp. The document carries all seven.

Dependency relationships are derived from the resolved dependency paths, so a transitive package is linked to the parent that actually pulled it in rather than to the project root.

The graph is complete for every supported package manager.

Edges come directly from the resolved lockfile graph for npm, pnpm, Yarn and Bun, so the graph is complete for every supported package manager. Measured on @lit-internal/monorepo (npm, 2060 packages), two large pnpm monorepos, a 1518-package Bun workspace monorepo, and three Yarn monorepos of 3000 to 5500 packages: every package has a parent edge except the root project itself, which correctly has none.

One caveat that is not package-manager specific. Packages that nothing else depends on are anchored to the root project, which requires the root to exist as a document element. If your package.json has no name field, no root package is emitted and those edges are dropped. Give the project a name to get a complete graph.

Why licenses are sometimes NOASSERTION​

npm records a declared license for every package in package-lock.json, so npm projects get real license data at no cost. pnpm, Yarn and Bun lockfiles do not carry licenses, so those projects show NOASSERTION, which is SPDX's explicit marker for "not determined" rather than a claim that no license exists.

licenseConcluded is always NOASSERTION. Concluding a license is a legal judgement about what a package actually is, not something a scanner can observe.

How vulnerabilities are represented​

SPDX 2.3 has no vulnerabilities section and no way to express CVSS or a severity rating. Findings are therefore attached per package as SECURITY external references of type advisory, which is the mechanism the SPDX specification defines for this.

Severity and the runnable fix command go into a package annotation instead. They cannot go into an external reference, because referenceLocator must be a URI and a shell command is not one.

This means the SPDX overlay carries less than the CycloneDX one. If you need machine-readable severity ratings in the SBOM itself, use --sbom cyclonedx, which has a native vulnerability model.

Inventory-only mode​

cve-lite . --sbom spdx --sbom-inventory-only

This omits the security references and annotations, leaving a pure inventory. Use it when the document feeds SBOM diffing, archival, or attestation, where you want the content to change only when your dependencies change, not when a new advisory is published.

The package list, licenses and relationships are then fully determined by the lockfile. Note that creationInfo.created and documentNamespace still differ between runs: SPDX requires both, and the namespace must be unique per document. SBOM comparison tools ignore those two fields by convention.

Combining with other outputs​

--sbom can be combined with --json and --sarif. All files are written in one scan:

cve-lite . --sbom spdx --sarif --json

--sbom cannot be combined with --report or with --fix.

GitHub Actions integration​

The OWASP/cve-lite-cli action does not expose an SPDX input yet; its cdx input covers CycloneDX only. Run the CLI directly for SPDX output:

- name: Generate SPDX SBOM
run: npx --yes cve-lite-cli@1 . --sbom spdx

- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
if: always()
with:
name: spdx-sbom
path: cve-lite-scan-*.spdx.json