Building an SBOM for Embedded Firmware
When a critical vulnerability lands in a widely used library, the first question every product team should be able to answer in minutes is: are we shipping it, and in which builds? For firmware, most teams cannot. There is no package-lock.json to grep, no dependency graph the build system tracks for you. A software bill of materials (SBOM) is how you build that answer once and keep it current — and regulators from the FDA to the EU are now asking to see it.
Why firmware SBOMs are harder than application SBOMs
For a Node or Python service, the package manager already holds a machine-readable manifest of every dependency and its version. A firmware image rarely does. Components arrive as vendored source trees copied into the repo years ago, static libraries handed over by a silicon vendor, a board support package pinned to one SDK release, and a bootloader nobody has rebuilt since the product launched. Versions live in a comment, a changelog, or nowhere at all. The result is that the parts most likely to contain an unpatched CVE — the third-party TCP/IP stack, the TLS library, the RTOS kernel — are exactly the ones your build system never names.
A firmware SBOM closes that gap. Done properly it lists every third-party and open-source component in the shipped image, each with a version and a supplier, in a format a tool can parse. The two formats regulators accept are SPDX (an ISO standard) and CycloneDX (an OWASP project). Pick one, generate it as part of the build, and treat it as an artifact of every release.
Two ways to generate it: from the build, and from the binary
The higher-fidelity approach is build-time generation. If your firmware is assembled with Yocto or Buildroot, the build system already knows every recipe, its version, and its license, and both can emit an SPDX document for the image. That is the SBOM you want, because it reflects what the build actually pulled in rather than a guess after the fact. Wire it into CI so a new document is produced on every tagged build and stored alongside the binary.
The complementary approach is binary composition analysis: point a tool at the finished image and let it identify components by their fingerprints. This is essential for the blobs you did not build — a vendor-supplied binary, a pre-2020 bootloader — and as a cross-check against the build manifest. The same firmware-analysis passes that surface embedded credentials also reveal library version strings; if you already run a hunt for hardcoded secrets in firmware, extend it to record every version banner and third-party string you find. The practical answer is to use both: build-time for what you control, binary analysis for what you inherit, and reconcile the two.
What a reviewer expects each entry to contain
NTIA’s widely cited “minimum elements” guidance is the practical bar. Every component needs a supplier, a component name, a version, a unique identifier, a dependency relationship, and the details of who authored the SBOM and when. The single most valuable field is the version, because vulnerability matching is version-precise: “we use OpenSSL” tells a reviewer nothing, while “OpenSSL 3.0.11” tells them exactly which advisories apply.
| Field | Why it matters | Where to get it for firmware |
|---|---|---|
| Supplier | Tells you who to watch for advisories | Recipe metadata, vendor delivery notes |
| Component name | The thing you are matching CVEs against | Build manifest, binary strings |
| Version | Vulnerability matching is version-precise | Recipe pin, version banner in the binary |
| Unique identifier | Lets tools correlate across databases | CPE or Package URL (purl) |
| Dependency relationship | Distinguishes shipped code from build tools | Build graph, SPDX relationships |
| Author and timestamp | Provenance and freshness of the document | Generated automatically by the tool |
Wiring it into the build
The goal is that no human generates the SBOM by hand. In Yocto, enabling the SPDX class makes the build emit an SPDX document for the image as a normal output. When you must analyze a binary you did not build — a delivered image, an OTA payload — a binary composition tool can produce a CycloneDX document you then merge. Below are the two commands that anchor each path.
# Yocto: emit an SPDX SBOM as part of the image build # add to local.conf, then build normally INHERIT += "create-spdx" bitbake core-image-minimal # Syft: generate a CycloneDX SBOM from an unpacked firmware root filesystem syft dir:./extracted-rootfs -o cyclonedx-json=firmware-sbom.cdx.json
An SBOM is a living artifact, not a checkbox
A bill of materials that is generated once and filed away has already gone stale. The point is continuous monitoring: feed each release’s SBOM into a scanner that matches components against vulnerability databases, so that when a new advisory drops you learn within hours whether a shipped build is affected and which one. Pair that with a VEX (Vulnerability Exploitability eXchange) document to record which flagged vulnerabilities are actually exploitable in your configuration, so your triage team is not drowning in noise from a library function you never call.
Getting this right the first time — a build that emits a clean SPDX or CycloneDX document, reconciled against binary analysis, and fed into ongoing monitoring — is the kind of foundational work a product security assessment is built to stand up. If you are staring at a firmware image and are not sure what is actually inside it, we can help you produce an SBOM a regulator will accept and a process that keeps it accurate. Start a conversation on our contact page.


