burgerlogo

How to Build an SBOM for Embedded Firmware (and Keep It Accurate)

How to Build an SBOM for Embedded Firmware (and Keep It Accurate)

avatar
Platanor Technologies

- Last Updated: November 4, 2026

avatar

Platanor Technologies

- Last Updated: November 4, 2026

featured imagefeatured imagefeatured image

A Software Bill of Materials sounds simple: a list of every software component in your product, with versions. For a web service, it mostly is. Package managers already know your dependencies, and tools like Syft or cdxgen can produce a decent SBOM in a CI step.

Embedded firmware is a different story. The dependency graph is messier, build toolchains are non-standard, and many components (vendor SDKs, RTOS kernels, hardware abstraction layers, bootloaders) never pass through a package manager at all. 

Here's a scenario we've seen more than once: a team runs an SBOM tool over their firmware repository, gets a clean-looking file with a dozen entries, and only later realizes that the TLS library, the RTOS, and the bootloader are not in it.

This article covers how to build an SBOM that reflects what is actually inside your firmware image, which format to use, how to keep it current release after release, and how to check that your vulnerability scanner really examined every component.

Firmware Teams Need an SBOM

There are two reasons, one practical and one regulatory.

The practical one: when a new vulnerability lands in a widely used library, the first question is "are we affected, and on which products?" Without a component inventory, answering that means digging through source trees and asking whoever wrote the code years ago. With an SBOM per release, it is a search.

The regulatory one: the EU Cyber Resilience Act (CRA) requires manufacturers of products with digital elements to identify and document the components in their products, including by drawing up an SBOM in a commonly used, machine-readable format that covers at least the top-level dependencies. Manufacturers must keep it as part of their technical documentation and provide it to market surveillance authorities on request. They are not required to publish it.

The SBOM is also what makes ongoing vulnerability monitoring possible. Once your component list is machine-readable, you can automatically match it against vulnerability databases, which is how you learn about a problem through your own monitoring rather than from a customer.

Choosing a Format: SPDX or CycloneDX

Two formats dominate, and both are widely accepted.

SPDX, maintained by the Linux Foundation, is an international standard (ISO/IEC 5962:2021). It is strong on licence information and common in open source ecosystems. It supports JSON, YAML, RDF, and a tag-value text format.

CycloneDX, maintained by OWASP and standardised as ECMA-424, was designed with security use cases in mind. It supports JSON and XML and is well supported by vulnerability tooling such as Dependency-Track.

The CRA does not prescribe a format today; it asks for a commonly used, machine-readable one, and the European Commission may specify a format later. In practice, the choice usually comes down to your downstream tools. Before standardizing, check which format your scanning and monitoring tools accept; some accept only one of the two.

Where Firmware SBOMs Go Wrong

Standard SBOM generators read package manifests and lock files: npm, pip, Maven, Cargo. A typical firmware project pulls in components through a mix of:

  • a vendor SDK (nRF Connect SDK, STM32Cube, ESP-IDF) that bundles its own versions of libraries;
  • an RTOS (FreeRTOS, Zephyr, ThreadX), often pinned to a version that differs from upstream;
  • open source libraries added as Git submodules or copied straight into the source tree;
  • precompiled chip-vendor libraries with no published version string;
  • your own application code on top.

None of this produces a manifest that generic tools understand. So a firmware SBOM is usually a combination of automated extraction and deliberate manual inventory.

A practical approach in three passes

1. Extract what automation can find. Run Syft or cdxgen over the source tree. They will pick up components they recognise: libraries with identifiable metadata, version references in CMake or Makefile fragments, Zephyr or nRF Connect module definitions. Treat this as a starting point, not the answer.

2. Add vendor components by hand. Your chip vendor's SDK, HAL, and any closed-source blobs need manual entries. Take version strings from SDK release notes or version headers. For Nordic, that means both the nRF Connect SDK version and the Zephyr version it bundles; for STM32, the STM32Cube package and the individual HAL driver versions.

3. Verify against the build output. Source trees often contain code that never makes it into the binary, and miss code pulled in at build time. Cross-check your list against what the build actually links: build logs and linker map files show what ended up in the final image. The SBOM should describe the shipped firmware, not the repository.

Each Entry

For every component, aim for:

  • Name, as the component is commonly known (FreeRTOS-Kernel, mbedTLS, MCUboot);
  • Exact version, never "latest";
  • An identifier vulnerability databases can match: a PURL (Package URL) and/or a CPE (Common Platform Enumeration);
  • Supplier;
  • Licence;
  • Hash of the source archive or binary, for integrity.

A minimal CycloneDX entry looks like this:

{
  "type": "library",
  "name": "FreeRTOS-Kernel",
  "version": "10.6.1",
  "purl": "pkg:github/FreeRTOS/[email protected]",
  "cpe": "cpe:2.3:o:amazon:freertos:10.6.1:*:*:*:*:*:*:*",
  "supplier": { "name": "Amazon Web Services" },
  "licenses": [{ "license": { "id": "MIT" } }],
  "hashes": [{ "alg": "SHA-256", "content": "<sha256 of the source archive>" }]
}

Not every component will have every field. Some vendor libraries have neither a PURL nor a CPE. Record what you have and mark what is missing: a complete and honest inventory beats a formally perfect one.

Check Your Scanner

This is the step most teams skip. Vulnerability databases key their records differently. Some match components mainly by PURL, which works well for package ecosystems such as npm, PyPI, or Debian. Others match mainly by CPE, the identifier used by NIST's National Vulnerability Database. Embedded C libraries pulled from GitHub or copied into a source tree often fall between the two.

The result can be a report that says "no vulnerabilities found" when, in fact, several components were never checked. When you review a scan:

  • look for a coverage figure, such as "6 of 8 components scanned," and treat anything below 100% as a to-do list rather than a pass;
  • see whether unmatched components are flagged as "not checkable" or silently counted as clean;
  • for important components, include both a PURL and a CPE where you can, so more databases can match them. Look the CPE up in NVD's CPE dictionary rather than guessing it: even one project can appear under more than one CPE name across records;
  • spot-check a component with a known, old vulnerability; if the scanner does not flag it, your identifiers or your scanner need attention.

Keeping it Current

An SBOM written once at launch goes stale with the first firmware update. Make it part of the release process:

  • Automate extraction in CI, so every firmware build produces an SBOM, even if manual entries still need review.
  • Version the SBOM with the firmware. Firmware v2.1.0 gets an SBOM for v2.1.0, archived with the release.
  • Review manual entries on each release, especially after SDK or RTOS upgrades, which can change many component versions at once.
  • Feed it into continuous monitoring, so new vulnerabilities against your exact component versions trigger alerts. Under the CRA, actively exploited vulnerabilities must be reported to authorities within 24 hours of the manufacturer becoming aware of them, which is only realistic if detection is automated. We break down the full timeline in our guide to CRA Article 14 vulnerability reporting.

Common Mistakes

Recording only the SDK version. An SDK release bundles components at their own versions. A vulnerability in Zephyr 3.4.0 will not match an entry that just says "nRF Connect SDK 2.5.0." List the bundled components individually.

Leaving out the bootloader. MCUboot, a vendor secure-boot implementation, or a custom bootloader is software with its own vulnerability history. If you rely on secure boot, the code doing the verifying belongs in the inventory too.

Generating from source instead of the build. It produces both false positives (code never linked) and gaps (code pulled in at build time).

Treating the SBOM as a one-off deliverable. Component versions change with every release; the SBOM has to change with them.

FAQ

Does the SBOM have to be public? No. Under the CRA, manufacturers must have one and provide it to market surveillance authorities on request. Publishing it is optional.

How deep does it need to go? The CRA sets the floor at top-level dependencies. For security purposes, going further is worth it, especially for open source libraries with their own vulnerability history, such as TLS stacks, network stacks, and compression libraries.

Which tools should we start with? Syft and cdxgen for automated extraction, Dependency-Track for loading SBOMs and monitoring vulnerabilities over time, and a simple template for documenting vendor components by hand.

Our RTOS is a proprietary one from the chip vendor. Does it belong in the SBOM? Yes. Anything that runs on the device belongs there. If the vendor does not publish vulnerability information, note that in your risk assessment: it means your monitoring coverage for that component is limited.

How long do we need to keep it? Under the CRA, technical documentation, including the SBOM, must be kept for at least 10 years after the product is placed on the market, or for the support period if that is longer.

Need Help Identifying the Right IoT Solution?

Our team of experts will help you find the perfect solution for your needs!

Get Help