Software Engineering 5 min read

Cascading SPDX Documents: When SBOMs Reference Each Other

How cascading SPDX documents work: ExternalDocumentRef, cross-document relationships, checksum-based resolution and the quirks of real-world SBOM data.

Cascading SPDX Documents: When SBOMs Reference Each Other

Cascading SPDX Documents: When SBOMs Reference Each Other

Cascading SPDX documents are the part of the SBOM topic almost nobody writes about, and the part where most tooling falls over in practice. Yet in delivery pipelines the pattern is the default: a release SBOM does not describe every package itself but references the SBOMs of its components, which in turn reference those of their container images. Building SBOM Lens meant living with this mechanism for months. This article explains how the chaining works technically and which traps real-world documents bring along.

Why multiple documents at all?

You could describe an entire release in one single SPDX document. In practice this fails on ownership and build timing. A component team generates its SBOM in its own pipeline, at component build time. The container image gets its SBOM during the image build, typically from Syft or Trivy. The release SBOM is created last and is supposed to tie the parts together without duplicating them. Duplication would also be semantically wrong: component SBOMs are signed, versioned artifacts. A release document that copies their content is stale the moment a component gets rebuilt.

SPDX 2.x models this with two building blocks: ExternalDocumentRef declares a foreign document, and relationships connect elements across document boundaries.

The mechanism: ExternalDocumentRef plus relationship

A release document declares its references in the header. Each reference consists of a local ID, the target document’s namespace and a checksum:

{
  "externalDocumentRefs": [
    {
      "externalDocumentId": "DocumentRef-billing-service",
      "spdxDocument": "https://example.org/spdxdocs/billing-service-2.4.1",
      "checksum": {
        "algorithm": "SHA1",
        "checksumValue": "d6a770ba38583ed4bb4525bd96e50461655d2759"
      }
    }
  ]
}

The actual link lives in a relationship that addresses the foreign element through the prefix:

{
  "spdxElementId": "SPDXRef-Package-release",
  "relationshipType": "CONTAINS",
  "relatedSpdxElement": "DocumentRef-billing-service:SPDXRef-DOCUMENT"
}

Read out loud: the release package contains the root element of the billing service document. A viewer that wants to resolve the cascade therefore has to find the matching document for every reference and resolve element IDs across prefixes. Sounds mechanical, but it raises two interesting questions: how do I recognize the right document, and what do I do when it is missing?

Resolution: checksum beats namespace

The namespace is a URI, but nothing guarantees it is resolvable, or that the file sitting on your disk was produced under exactly that URI. The checksum is more reliable: it identifies the exact document revision, independent of file name and storage location. In SBOM Lens we therefore match against the SHA1 checksums of all loaded files first and only fall back to namespace comparison afterwards. That ordering is not cosmetic. Matching by namespace first risks wiring a release to an outdated revision of a component SBOM, and silent mismatches are precisely what you do not want in a supply chain analysis.

If a reference stays unresolved, the worst reaction is an empty node without context. Better is an explicit placeholder that shows which document is missing and offers to fetch it by URL or accept a dropped file. Missing pieces are not an exception in a cascade; they are a state your tooling has to handle.

The quirks of real-world documents

The specification at spdx.dev is precise. The documents that generators actually emit are not always. Four patterns keep showing up.

First, checksum spelling: some tools write SHA1: abc..., others SHA1:abc..., and the algorithm casing varies. Second, duplicate SPDXIDs within one document, usually a generator bug that strictly speaking invalidates the document but is ignorable in practice. Third, versions that are absent from the versionInfo field and hide only in the purl inside externalRefs. If you want to detect version conflicts across a cascade, you have to parse purls or you will see nothing. Fourth, relationship types outside the specification, often from older tool versions.

A parser has two options here: reject strictly, or load tolerantly and report the findings. For an analysis tool only the second option is usable. The NTIA minimum elements provide the right assessment frame: instead of rejecting documents, measure which mandatory fields are covered and make the gaps visible.

Conclusion

Cascading SBOMs are not an exotic corner case; they are the natural structure of pipelines with distributed ownership. Evaluating them takes three things: checksum-before-namespace resolution, a deliberate strategy for missing documents, and tolerance for the quirks of real generators. To see what it feels like when it works, load the bundled example cascade in SBOM Lens; it takes two minutes. And if your pipeline produces SBOMs but no usable cascade structure yet, we help you build it: software engineering at EverBright.

Frequently Asked Questions

What is an ExternalDocumentRef in SPDX?

ExternalDocumentRef is the SPDX mechanism for one document to reference another. The reference consists of a local ID, the target’s document namespace and a checksum of the target file. Relationships can then connect elements across document boundaries, for example linking a release package to the SBOM of a component.

Why resolve SBOM references by checksum instead of namespace?

The checksum identifies the exact revision of a document, while a namespace is just a name that may point at outdated or divergent files. Matching by checksum first prevents silent mismatches between a release and its component SBOMs, which namespace-based matching can let slip through unnoticed.

How should tools handle missing documents in an SBOM cascade?

Missing references should remain visible as explicit placeholders instead of being silently dropped. Good tooling shows which document is missing and offers to fetch it by URL or match a local file. That keeps the analysis usable and documents the gap until the missing SBOM becomes available.

Are cascading SBOMs mandatory?

No regulation prescribes cascades. They emerge naturally once components are built in separate pipelines and their SBOMs are generated at build time. A release document then references existing artifacts instead of copying their content, which keeps signatures and version states intact.

#SPDX #SBOM #ExternalDocumentRef #Supply Chain Security
Share:
Sergej Bardin

Sergej Bardin

CEO · AI Strategy & IT Consulting

Helping mid-sized companies adopt AI and shape their cloud strategy. Focus on practical decisions over hype.

AI StrategyMCPRAGMulti-CloudIT ConsultingMid-Market