Skip to content
folhas.io
secure by design for connected products· Articles

Articles

SBOM for embedded firmware: what the CRA requires and how to generate one without lying

The software bill of materials the CRA demands is not written by hand. For firmware, the only credible SBOM comes out of the build system, and binary scanning serves for what we did not build ourselves.

Published
Updated

Update note (23 Oct 2026). The list of minimum elements now follows the final version published by CISA and partners on 29 Jul 2026. We have added the limit of the version number when fixes ship without a new version, with the PHP case of October 2026.

The Cyber Resilience Act requires, in Annex I, Part II, that the manufacturer identify and document the components of the product, including by means of a machine-readable SBOM covering, at the very least, the top-level dependencies. The SBOM does not have to be public: it forms part of the technical documentation and is handed to the market surveillance authorities on request.

For a web application, this is an afternoon’s work. For firmware, it is where most manufacturers will fail, and fail in a specific way: by delivering an SBOM that looks complete and is not.

The firmware problem

An embedded firmware image typically carries no package manager on board. There is no dpkg or apk database for a scanner to consult. When you point a composition analysis tool at an extracted rootfs, coverage depends on what survived compilation: binary signatures, version banners, strings. The result is large, silent gaps, because the tool does not know what it failed to find.

The practical consequence: an SBOM generated by binary scanning of firmware is a starting point, and it does not suffice as a conformity document.

The rule that solves almost everything

The credible SBOM comes out of the build system, not out of the binary. The build system is the only place that knows exactly what went into the image, with which version, with which patches and under which licence.

The three dominant ecosystems already do this natively:

  • Yocto: the create-spdx class has been included by default via INHERIT_DISTRO since Kirkstone (4.0). A normal image build produces an .spdx.json in the deploy directory, derived from the same inputs that produced the binary. For those who prefer CycloneDX, the meta-cyclonedx meta-layer produces CycloneDX 1.6/1.7 SBOMs with corrected CPE and purl for better matching against the NVD.
  • Buildroot: make legal-info generates the complete manifest of packages, versions and licences; the cyclonedx-buildroot converter turns it into CycloneDX JSON.
  • Zephyr: west spdx generates SPDX 2.3 documents for the application, Zephyr, the build and module dependencies. Mind the documented limitation: vendor binary blobs and out-of-tree libraries are not included; they have to be added as external components or documented as gaps.

And what about what we did not build?

This is where binary analysis has its legitimate place: the bootloader shipped by the SoC vendor, the radio blob, a partner’s pre-compiled library. For those, a binary composition tool produces a document that is then reconciled with the build manifest. The method is two inventories, one of what we control and one of what we inherited, merged into a single document, with the gaps identified as such.

Diagram: the build system generates the SBOM for what we build; the binary scan inventories the inherited components; the two are reconciled into a single final document, with gaps declared.
Two inventories, one document: the build answers for what we built, the scan for what we inherited.

Which format

The two formats accepted in practice are SPDX (the ISO/IEC 5962 standard) and CycloneDX (ECMA-424). The German technical guideline BSI TR-03183-2, the document most cited by auditors and notified bodies in the CRA context, recommends CycloneDX 1.6+ or SPDX 3.0.1+. CycloneDX has a native firmware component type and built-in VEX support, which makes it the natural choice for the CRA workflow; Yocto produces SPDX natively. The pragmatic answer for Yocto users: generate both.

As a reference for minimum content, the US “minimum elements” remain the practical yardstick. The final version, published on 29 July 2026 by CISA with the NSA, the FBI and international partners (including BSI, ANSSI and NCSC-NL), supersedes the 2021 NTIA version. Per component: component producer (formerly “supplier”; CISA itself notes it is not the same as the CRA “manufacturer”), name, version, identifiers (purl and/or CPE), dependency relationship, hash (value and algorithm) and licence. About the SBOM itself: author and author signature, timestamp, SBOM version, data format and format version, tool name and version, and generation context (“before build”, “build”, “after build”). That last field is this article’s rule expressed as data: it says whether the SBOM came from the build or from a binary scan.

The field that is worth the most is the version: matching against vulnerabilities is precise to the number. “We use OpenSSL” says nothing; “OpenSSL 3.0.11” says exactly which advisories apply.

The version number has a known limit. Many Linux distributions, and build systems such as Yocto, apply security fixes as patches without changing the component’s upstream version. In October 2026, when fixing the flaws known as PHP2Shell, the PHP project also published minimal fixes for the 7.4, 8.0 and 8.1 branches, already end-of-life, as branches in the source repository, with no numbered release and no tarball. In such cases the version alone does not tell whether the fix is applied. The SBOM generated in the build should therefore also record the patches applied (the build system knows which they are), and the status of each vulnerability in the product is communicated in VEX: affected, not affected, fixed or under investigation. In the Linux kernel, backports also scramble the affected version ranges in the CVE records, as article 10 shows.

The list of possibly affected products that NCSC-NL published for those flaws is a public example of the format: SPDX documents in which the status of each entry derives from VEX relationships. Article 09 looks at the case from the manufacturer’s side.

What this means for manufacturers

Three decisions we took for our own product, and that we recommend to any team:

  1. Generated in the pipeline. The SBOM is generated on every tagged build, stored next to the binary, and no human writes or corrects it by hand. If something in the SBOM is wrong, the fix goes into the recipe or build configuration, and the next build comes out right.
  2. Wired to monitoring. Loaded into a CVE tracking tool (Dependency-Track, for example), the SBOM becomes the alerting system that feeds the CRA’s own vulnerability handling obligation, and the trigger for the 24h/72h reporting process.
  3. Declared gaps. A blob we cannot decompose goes into the SBOM as its own line, flagged as a gap. Before a market surveillance authority, that flag is what shows what the inventory covers and what it leaves out.

Sources