This is the fourth and final post in our series on managing SBOMs at scale, where we’ll dive into the intricacies of creating, managing, and ingesting complex dependent SBOMs. If you haven’t read it yet, check out our first three posts “DevSecOps Roadmap: Do I Really Need SBOMs?”, “DevSecOps Roadmap: Generating SBOMs”, and “DevSecOps Roadmap: Ingesting, Managing, and Monitoring SBOMs”.
As SBOMs become more common, it’s clear that references between them will play a key role in representing and managing applications and systems at scale. How do you visualize what could become a complex web of interconnected SBOMs? How do you establish and maintain those links? Most importantly, how can this process be automated at scale when consuming third-party SBOMs? We’ll explore these questions for both SPDX and CycloneDX and use the SOOS platform to illustrate how this can be done simply and efficiently.
What is a linked or dependent SBOM?
First, let’s define a dependent SBOM—technically referred to as an “external document reference” in SPDX or a “BOM-link” in CycloneDX. I prefer the term “dependent SBOM” because it clearly conveys the concept: an SBOM that relies on one or more other SBOMs, much like dependencies in software development. These dependent SBOMs may themselves have additional dependencies, creating a chain. To further complicate matters, each dependent SBOM likely comes with its own component dependency tree!
How are dependent SBOMs defined?
Dependent SBOMs are referenced from a parent SBOM using syntax that allows the exact SBOM version to be specified. For SPDX this is through the “External document references field” (externalDocumentRef) and for CycloneDX it is through the externalReferences field (either on the BOM itself or on an individual component.
How do you link SBOMs with SPDX?
The external document ref is a URL defined by a few key parts, the document being referenced (typically the name and version separated by hyphens), and a UUID checksum. The base URL may be spdx.org/spdxdocs or your own domain, but should always contain spdxdocs, for example: mycompany.com/spdxdocs. The full URL does not have to point to the SBOM, but it can if the SBOM is publicly available.
Example:
https://spdx.org/spdxdocs/spdx-tools-v1.2-019fbf2a-bf47-4f2d-8505-03c95d1
In this example spdx-tools is the application, v1.2 is the version and 019fbf2a-bf47-4f2d-8505-03c95d197703 is a UUID for the original SBOM document.
Let’s say we have an application, myDatabaseModule and we have generated the following SBOM, in this case from the SOOS platform, but it could be from any SBOM generation tool. Note the documentNamespace
{
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"name": "myDatabaseModule",
"documentNamespace": "https://app.soos.io/spdxdocs/soos/myDatabaseModule-5.0.28.2-683d4347-bac3-4eba-b4e7-c335b08b7197",
"comment": "",
"creationInfo": {
"created": "2024-11-11T15:49:17Z",
"creators": [
"Organization: SOOS LLC",
"Person: Sooster Support (support@soos.io)",
"Tool: SOOS SBOM-34.4.25.2"
],
"comment": "SOOS SBOM scan against myDatabaseModule@5.0.28.2",
"licenseListVersion": "3.9"
},
... OMITTED FOR BREVITY ...
}
Now imagine we have another application, myComplexApp, which depends on myDatabaseModule. The SBOM for myComplexApp can reference the myDatabaseModule using externalDocumentRefs.
{
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"name": "myComplexApp",
"documentNamespace": "https://app.soos.io/spdxdocs/soos/myComplexApp-1.0.0-61d2dda6-e618-43f1-bbdb-bd198e9c833b",
"comment": "",
"creationInfo": {
"created": "2024-11-11T15:49:17Z",
"creators": [
"Organization: SOOS LLC",
"Person: Sooster Support (support@soos.io)",
"Tool: SOOS SBOM-34.4.25.2"
],
"comment": "SOOS SBOM scan against myComplexApp",
"licenseListVersion": "3.9"
},
"externalDocumentRefs": [
{
"externalDocumentId": "DocumentRef-myCoreModule.spdx.json-2024-11-11T18:20:01",
"checksum": {
"algorithm": "SHA256",
"checksumValue": "20...89"
},
"spdxDocument": "https://app.soos.io/spdxdocs/soos/myCoreModule-0.0.5-ab7be9d4-11b1-441d-957e-51ff12262911"
},
{
"externalDocumentId": "DocumentRef-myDatabaseModule.spdx.json-2024-11-11T16:27:26",
"checksum": {
"algorithm": "SHA256",
"checksumValue": "46...8c"
},
"spdxDocument": "https://app.soos.io/spdxdocs/soos/myDatabaseModule-5.0.28.2-683d4347-bac3-4eba-b4e7-c335b08b7197"
}
]
... OMITTED FOR BREVITY ...
}
This is telling us that the SBOM we generated for myDatabaseModule is a dependency at version 5.0.28.2. Note that there is another dependency in here as well, myCoreModule which follows the same referential pattern.
If you’re using the SOOS platform, we will automatically create a dependent project link when external document references are encountered and the dependent SBOM has previously been scanned. If the dependent SBOM version has previously been scanned, then SOOS will even locate the scan and present high level details in the UI with a link back to the scan!

You may notice in our screenshot, that myDatabaseModule itself has dependent projects, which in turn have dependencies as well. SOOS will automatically build out this full hierarchy as long as the SBOMs have previously been scanned and are referenced appropriately in the SBOM. These also do not need to be SBOM scans, they could also be traditional SCA scans, or Container scans and can also be adjusted without using the SBOM external reference concept.
How do you link SBOMs with CycloneDX?
Other than the reference syntax, CycloneDX supports pretty much exactly the same functionality as SPDX.
The bom links contained in the externalReferences array are each defined by a url and a set of hashes. The main identifier in the url is the document serialNumber, followed by a version (unlike SPDX, this is the version of the SBOM document, not the application version). Optionally a component reference within the dependent SBOM may be included at the end. Note that CycloneDX allows external references at the document, or component level.
Example:
"externalReferences": [
{
"type": "bom",
"url": "urn:cdx:019fbf2a-bf47-4f2d-8505-03c95d197703/8#myComponent",
"hashes": [
{
"alg": "SHA-512",
"content": "234...299"
}
]
}
]
In this example 019fbf2a-bf47-4f2d-8505-03c95d197703 is the serial number, 8 is the SBOM version and myComponent is the optional component name being referenced (for SOOS this is the referenced project name within the SOOS platform).
Following from our SPDX example above we could define a myDatabaseModule SBOM as follows (note the serialNumber and metadata > component > name).
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:07cd8260-a89a-405c-81f2-0e65eee540fd",
"version": 1,
"metadata": {
"component": {
"type": "application",
"publisher": "myOrgName",
"name": "myDatabaseModule",
"version": "5.0.28.2",
"description": "SOOS SBOM scan against myDatabaseModule@5.0.28.2",
"hashes": [
{
"alg": "SHA-256",
"content": "db...91"
}
]
}
... OMITTED FOR BREVITY ...
},
"components": [ ... OMITTED FOR BREVITY ... ],
"externalReferences": [ ... OMITTED FOR BREVITY ... ],
"dependencies": [ ... OMITTED FOR BREVITY ... ]
}
Then the SBOM for myComplexApp, which depends on myDatabaseModule would reference myDatabaseModule (and myCoreModule) using the externalReferences array.
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:a4cd250e-42d2-46a5-bee3-50bb2355a84a",
"version": 1,
"metadata": {
"component": {
"type": "application",
"publisher": "myOrgName",
"name": "myComplexApp",
"version": "8.0.0",
"description": "SOOS SBOM scan against myComplexApp@8.0.0",
"hashes": [
{
"alg": "SHA-256",
"content": "86...f4"
}
]
}
... OMITTED FOR BREVITY ...
},
"externalReferences": [
{
"url": "urn:cdx:01925f0c-1eda-43cd-a961-da3275bc733c/1#myCoreModule",
"type": "bom",
"comment": "Refer to the SBOM from the SOOS project myCoreModule with the serial number urn:uuid:01925f0c-1eda-43cd-a961-da3275bc733c",
"hashes": [
{
"alg": "SHA-256",
"content": "f5...57"
}
]
},
{
"url": "urn:cdx:07cd8260-a89a-405c-81f2-0e65eee540fd/1#myDatabaseModule",
"type": "bom",
"comment": "Refer to the SBOM from the SOOS project myDatabaseModule with the serial number urn:uuid:07cd8260-a89a-405c-81f2-0e65eee540fd",
"hashes": [
{
"alg": "SHA-256",
"content": "0d...bc"
}
]
}
],
"components": [ ... OMITTED FOR BREVITY ... ]
"dependencies": [ ... OMITTED FOR BREVITY ... ]
}
This is telling us that the SBOM we generated for myDatabaseModule is a dependency at serial number 07…fd. In SOOS, If we have previously scanned this SBOM then we will have mapped it to version 5.0.28.2 and then could automatically create this link. myCoreModule follows the same reference pattern as well.
Putting it all together – the system at scale
Both CycloneDX and SPDX provide methods for identifying and linking external SBOMs, but it takes a system (or systems) capable of tracking SBOM identifiers and versions both on the export side as well as on the import side.
SBOM Generation
While we explored simplified examples of manually creating references between components, this approach quickly becomes unmanageable at scale. What’s truly needed is the ability to automatically track dependencies across projects—whether SCA scans, container scans, or other ingested SBOMs. During the export process, this information should be leveraged to automatically generate SBOMs that include dependency data using the externalDocumentRef field in SPDX or the externalReferences field in CycloneDX.
SBOM Ingestion
As we’ve seen, the ingestion process must track projects and dependencies between them as SBOMs are ingested and create the links between them, essentially re-creating and often augmenting what was previously exported. The ability to ingest a set of dependent SBOMs, then link some of your own dependent projects and re-export a new dependent SBOM with all projects included is essential.
Luckily, SOOS handles all of this for you automatically. Define project dependencies for SCA, Containers, and SBOMs; generate SBOMs with external references; and ingest and link SBOMs based on external references automatically.
Wrap-up
Whether you’re representing complex systems or simply ingesting a few SBOMs to view alongside the applications that use those components, leveraging external SBOM references and tools that support relationships between SCA, Container, and SBOM projects enables comprehensive insights across software ecosystems, and flexible export capabilities.
Series Wrap-up
This series focused on high-level processes and best practices rather than detailed implementation specifics for SBOMs. The aim was to provide an overview of SBOM capabilities, the business goals they support, and practical guidance for implementing these processes. The real advantage of SBOMs lies in their role as a standardized data contract for sharing information about specific software. This flexibility allows organizations to use various tools for creating, ingesting, managing, and monitoring SBOMs. Choosing the right tool that aligns with your business needs is key to maximizing security and operational benefits.