Quality and security
At the end of 2021, the Log4Shell vulnerability in the widely used Java library Log4j became public. Many companies spent days searching: is the library in one of our products – and in which version? Anyone with a software bill of materials answered in minutes. Under the Cyber Resilience Act, this list becomes mandatory for manufacturers.
What belongs in an SBOM
The CRA defines the software bill of materials (SBOM) as a formal record of the details and supply chain relationships of the components included in the software of a product. In practice, it is a machine-readable list of all libraries, packages and modules. For each component, it contains at least:
- name and version,
- manufacturer or supplier,
- a unique identifier, usually a package URL such as
pkg:nuget/Newtonsoft.Json@13.0.3, - a checksum of the delivered file,
- the licence,
- the dependencies on other components.
What does not belong in it are vulnerabilities. Whether a flaw actually affects your product is recorded separately, for example in a VEX document (Vulnerability Exploitability eXchange).
What the CRA requires – and what it doesn't
Annex I, Part II, point 1 requires an SBOM in a commonly used, machine-readable format covering at the very least the top-level dependencies. It is part of the technical documentation and must be provided to the market surveillance authority upon reasoned request. You do not have to publish it. If you make it available to your customers, the user information must state where it can be found. The regulation does not yet prescribe a fixed format; the Commission may specify one in an implementing act.
A good benchmark already exists in the German BSI technical guideline TR-03183-2. Version 2.1.0 requires JSON or XML in CycloneDX from version 1.6 or SPDX from version 3.0.1. For each component it requires, among other things, creator, name, version, filename, licence, dependencies and a SHA-512 hash. Dependencies are resolved recursively, at least up to and including the first component outside your scope of delivery.
CycloneDX or SPDX?
| CycloneDX | SPDX | |
|---|---|---|
| Origin | OWASP, standardised as ECMA-424 | Linux Foundation, version 2.2.1 as ISO/IEC 5962:2021 |
| Focus | security and supply chain, VEX possible in the same format | originally licences and compliance, security as well since version 3 |
| Current version | 1.7 | 3.0.1 |
Both formats satisfy the CRA; what matters is what your tools and customers process. For matching against vulnerability databases, for example with OWASP Dependency-Track, CycloneDX is common, while licence checks traditionally favour SPDX. Conversion is possible, but not always lossless.
Generating the SBOM in the build
An SBOM maintained by hand is wrong after the next package update. It belongs in the build – for exactly the version you ship. For .NET there is the CycloneDX command-line tool, for Python cyclonedx-py, and for SPDX, for example, Microsoft's sbom-tool:
# .NET: install the tool once, then run it in every release build
dotnet tool install --global CycloneDX
dotnet-CycloneDX Product.sln -o sbom -fn product.cdx.json -F Json -t -sn Product -sv 2.4.0
# Python: from requirements.txt
cyclonedx-py requirements requirements.txt --of JSON -o sbom/tool.cdx.json
-sv sets the product version, -t leaves out test projects. A small script then checks whether the SBOM is complete and fails the build if information is missing – for example licences the tool could not resolve:
import json
import sys
with open("sbom/product.cdx.json", encoding="utf-8") as file:
components = json.load(file).get("components", [])
gaps = []
for component in components:
name = component.get("name", "?")
for field in ("version", "purl", "licenses", "hashes"):
if not component.get(field):
gaps.append(f"{name}: {field} missing")
print(f"{len(components)} components checked, {len(gaps)} gaps")
for line in gaps:
print(line)
sys.exit(1 if gaps else 0) # gaps make the build fail
What the tools don't find
Tools read package managers. Anything that bypasses them is missing: copied DLLs, C and C++ libraries, purchased modules, firmware, bundled runtimes. Record these once in a supplementary file that the build merges with the generated SBOM, for example with the merge command of the CycloneDX CLI. For CAD add-ins, the opposite applies: you do not ship the AutoCAD or Revit DLLs you build against. They do not belong in your SBOM as components, but they do belong in the documentation as a prerequisite.
And then the SBOM has to be used: archive it for each release with the technical documentation and match it against vulnerability databases continuously – new vulnerabilities also affect versions already running at your customers.
Note: This article gives a technical overview and is not legal advice.
Typical mistakes
- Generating the SBOM from the source code instead of the build. It then does not describe what is actually shipped.
- Recording only direct packages. Many vulnerabilities hide in transitive dependencies.
- One SBOM for all versions. Every shipped version needs its own.
- Writing vulnerabilities into the SBOM instead of a separate VEX document.
- Forgetting copied DLLs and C++ libraries because no package manager knows them.
How we help
- SBOM in the build: We set up generation for .NET, C++ and Python, add missing components and check completeness – as part of our services for the Cyber Resilience Act.
- Vulnerabilities: We assess findings for your product and fix them: Quality review and code review.
- Getting started: A fixed-price consulting day shows where your products stand. Or arrange an initial consultation.
Sources: Regulation (EU) 2024/2847, Art. 3(39), Art. 13(24), Annex I Part II point 1, Annex II point 9, Annex VII and recital 77, EUR-Lex; BSI TR-03183-2, version 2.1.0; CycloneDX; SPDX. As of October 2026.
