SBOM for Firmware: A Working Pipeline for the EU Cyber Resilience Act

The notice arrives at 16:30 on a Thursday. A researcher has published details of a flaw in a widely used cryptographic library, there are reports that it is being exploited, and your sales manager asks a simple question: do we ship it?
Under the EU Cyber Resilience Act, that question now has a clock attached. Since 11 September 2026, manufacturers of products with digital elements sold in the EU must report actively exploited vulnerabilities in their products, with an early warning within 24 hours of becoming aware and a fuller notification within 72 hours. The duty applies to products already on the market, not only to new ones. A team that can answer the sales manager's question in ten minutes has a software bill of materials that works. A team that needs three days to open old build folders has a spreadsheet that does not.
This is a field guide to building an SBOM pipeline for firmware: the inventory, the format, the checks, the matching against advisories, the triage and the reporting clock. It is written for teams that build embedded products, where components hide in places a web application's package manager never sees, and where the build system, the vendor libraries and the bootloader all need to appear in one list. Each stage follows the same pattern: what it is, how to do it, how it fails, and what to check. The code runs, and where the regulation or a standard is cited from public sources, I say so, because the legal details are the part you must confirm in the primary text.
What Changed on 11 September 2026
The Cyber Resilience Act is Regulation (EU) 2024/2847. It entered into force on 10 December 2024 and applies in stages. Public summaries and law-firm briefings agree on the dates that matter.
Date | What applies | Why it matters to a firmware team |
10 December 2024 | The regulation entered into force | The phased clock started |
11 June 2026 | Rules on conformity assessment bodies | Relevant if your product needs third-party assessment |
11 September 2026 | Reporting of actively exploited vulnerabilities and severe incidents, Article 14 | Already in force, and covers products already on the market |
11 December 2027 | Full application: security requirements, technical documentation, conformity assessment, CE marking, SBOM and vulnerability handling | The date by which the whole product file must exist |
The reporting duty works in steps. On becoming aware that a vulnerability is being actively exploited, the manufacturer sends an early warning within 24 hours and a fuller notification within 72 hours, filed once through a single reporting platform operated by the European Union Agency for Cybersecurity, which routes it to the relevant national response team. A final report follows later, and the deadlines for it differ by case, so read Article 14 for those.
Administrative fines can reach 15 million euros or 2.5 percent of worldwide annual turnover, whichever is higher, for breaches of the essential requirements and the manufacturer obligations. Non-EU manufacturers are in scope when they sell into the EU.
An SBOM is not itself the reporting duty. It is what makes the duty possible. You cannot report that your product is affected until you know what is inside it, and under time pressure you will not reconstruct that from memory. Standardised formats, CycloneDX and SPDX, are the expected carriers, and the German federal cybersecurity agency's technical guideline TR-03183, particularly its second part on SBOMs, is widely used as the practical reference while the Commission's implementing acts develop. The Commission also published practical guidance for manufacturers in July 2026.
The reporting duty started in September 2026 and the full obligations follow in December 2027. A team that begins its SBOM in 2027 will be answering the first real notice without one.
What an SBOM Contains
A software bill of materials is a machine-readable list of the components in a product, with enough detail to identify each one and the relationships among them. It is the ingredients list for software.
The widely used baseline for what must be in it is the minimum elements published by the United States government in 2021, and most CRA guidance builds on it. Each component needs a supplier, a name, a version, at least one unique identifier, and a record of what depends on what. The SBOM as a document needs the author of the data and a timestamp. For firmware, add two more fields that matter in practice: a cryptographic hash of the component, so that you can prove which binary you meant, and a licence.
Element | What it is | Why firmware needs it |
Supplier | Who produces the component | Tells you whom to ask for a fix |
Name and version | What it is, exactly | The unit that advisories refer to |
Unique identifier | A package URL or a platform enumeration | Lets tools match a component to an advisory |
Dependency relationship | What includes or links what | Shows how far a vulnerable library spreads |
Hash | A SHA-256 of the component | Distinguishes your build from a similarly named one |
Licence | The terms of use | Compliance and, for some, source obligations |
Author and timestamp | Who produced the list, and when | Shows how fresh the list is |
Two formats dominate. CycloneDX comes from a security community and is compact and tool friendly. SPDX comes from the licensing community and is a formal international standard. Either is acceptable to most tools, and many pipelines produce both. This guide uses CycloneDX JSON, and the stages apply equally to SPDX.
A companion document called a vulnerability exploitability exchange statement, usually abbreviated VEX, records the result of your analysis: whether a given vulnerability in a given component actually affects your product. It matters more than it sounds, as the triage stage shows.
Why Firmware Is Harder Than Web Software
A web application's dependencies live in a package manager, and tools read them automatically. Firmware's dependencies live everywhere. At least eight places hide components that a standard scanner will miss.
Where components hide | Example | The usual miss |
Real-time kernel or operating system | A kernel from a vendor or a community | Listed under the product name, version missing |
Vendor software development kits | A silicon or module vendor's drivers and stacks | Provided as binaries with no component list |
Bootloader and secure-boot code | First- and second-stage loaders | Treated as part of the hardware, not the software |
Statically linked libraries | A compression or cryptography library compiled in | No trace in the final image |
Wireless and protocol stacks | A radio stack supplied by a module maker | Binary blob, version only in a log line |
Copied source files | A function pasted from an open-source project | No record anywhere |
Build tools and runtime support | A compiler runtime linked into the image | Seen as tooling, not content |
Gateway operating-system images | A full Linux distribution on a larger device | Hundreds of packages, with the base image unrecorded |
The practical result is that no single scanner produces a trustworthy firmware SBOM. A pipeline combines three sources: what the build system knows, what the vendors tell you, and what you find by analysing the finished binary.
Stage 1: Inventory at the Source
What it is. Capturing the component list during the build, from the tools that already know it, and not afterwards by examining the finished image.
How to do it. Use the build system's own support where it exists. Embedded Linux builds based on the Yocto Project can emit an SPDX document for the image through a standard class. The Zephyr project's west tool can generate an SPDX document for a build. For builds driven by CMake, a vendor SDK or a hand-written makefile, no automatic generator is complete, and the practical answer is a manifest file kept in the repository, listing each third-party component with its version, source and hash, and checked at every release.
For vendor binaries, ask in writing for a component list and the version of each library inside, and make it a condition of purchase for new parts. For copied source files, adopt a rule that any third-party code enters the repository only with a manifest entry.
How it fails. The commonest failure is generating the SBOM from the final image with a tool that guesses, which misses statically linked libraries and invents versions from strings. The second is the build that nobody can reproduce, so the list describes a build that no longer exists.
Check before moving on. Pick three components you know are in the product, one open-source library, one vendor binary and one copied file, and confirm that each appears with its correct version.
Stage 2: Normalise to One Format
What it is. Converting the different sources into a single document in a standard format, with consistent identifiers.
How to do it. Write one small converter that reads your manifest and the generated lists and emits CycloneDX JSON. Give every component a package URL as its unique identifier and use the same value as its reference inside the document, so that the dependency graph points to real entries. The code below builds a small bill for a firmware image.
def make_component(name, version, supplier, purl, sha256=None, license_id=None):
c = {"type": "library", "name": name, "version": version, "supplier": {"name": supplier},
"purl": purl, "bom-ref": purl}
if sha256:
c["hashes"] = [{"alg": "SHA-256", "content": sha256}]
if license_id:
c["licenses"] = [{"license": {"id": license_id}}]
return c
def make_bom(product, version, components, deps):
return {"bomFormat": "CycloneDX", "specVersion": "1.5", "version": 1,
"metadata": {"timestamp": "2026-11-11T09:00:00Z",
"authors": [{"name": "Build pipeline"}],
"component": {"type": "firmware", "name": product, "version": version,
"bom-ref": product + "@" + version}},
"components": components, "dependencies": deps}
comps = [make_component("zlib", "1.3.1", "zlib project", "pkg:generic/zlib@1.3.1", "a" * 64, "Zlib"),
make_component("mbedtls", "3.6.0", "Mbed TLS contributors", "pkg:generic/mbedtls@3.6.0", "b" * 64, "Apache-2.0"),
make_component("rtos-kernel", "10.6.2", "Kernel vendor", "pkg:generic/rtos-kernel@10.6.2")]
bom = make_bom("field-gateway-fw", "2.4.0", comps,
[{"ref": "field-gateway-fw@2.4.0", "dependsOn": [c["bom-ref"] for c in comps]}])
How it fails. Inconsistent identifiers. A library recorded as zlib in one list and as libz in another is two components to a matching tool. Pick a naming rule and enforce it in the converter.
Check before moving on. Run the converter twice on the same inputs and confirm that the output is identical. A pipeline that gives different SBOMs for the same build cannot be trusted.
Stage 3: Check the Minimum Elements
What it is. An automatic test, run on every build, that fails when the SBOM is incomplete.
How to do it. Write the check as code and make it a gate in your release process. It should test for the document-level elements and for each component's supplier, version, identifier and hash, and for the presence of dependency relationships.
def check_minimum(bom):
missing = []
if not bom.get("metadata", {}).get("timestamp"):
missing.append("timestamp")
if not bom.get("metadata", {}).get("authors"):
missing.append("author of SBOM data")
for c in bom.get("components", []):
tag = c.get("name", "?")
if not c.get("supplier", {}).get("name"): missing.append(tag + ": supplier")
if not c.get("version"): missing.append(tag + ": version")
if not (c.get("purl") or c.get("cpe")): missing.append(tag + ": unique identifier")
if not c.get("hashes"): missing.append(tag + ": hash")
if not {d["ref"] for d in bom.get("dependencies", [])}:
missing.append("dependency relationships")
return missing
print(check_minimum(bom)) # ['rtos-kernel: hash']
On the sample bill the check passes everything except one line: the vendor kernel has no hash, because the vendor supplied a binary and no checksum. That is realistic, and it is exactly the kind of gap the gate should surface. Remove the supplier of one component, the author and the dependency list, and the same function reports all of them: the missing author, the missing supplier for that component, the kernel hash and the missing dependency relationships.
How it fails. Teams make the gate advisory, and it is ignored. Make it blocking for the elements you control and tracked as a defect, with a named owner, for the ones that depend on a vendor.
Check before moving on. Deliberately break an SBOM in three ways and confirm the gate reports each.
Stage 4: Bind the SBOM to the Release
What it is. Tying each SBOM to exactly one firmware image, so that a vulnerability notice can be answered for the version a customer actually runs.
How to do it. Put the SHA-256 of the released firmware image in the SBOM's metadata and store the two together in the release record. Sign the SBOM with the same key discipline as the firmware, so that a later reader can tell it is the one produced at release. Keep every released SBOM, because the customers running version 2.1.0 are not running 2.4.0, and the notice may concern the older one. Store them in a system that can be searched by component, so that the question do we ship this library becomes a query.
How it fails. Keeping only the latest SBOM. A flaw in a library that you replaced eight months ago still affects every unit in the field running the old image.
Check before moving on. Ask: which released versions contain mbedtls 3.6.0? If you cannot answer in under a minute, the storage needs a search.
The question at 16:30 is never about the latest release. It is about every release still running in the field, so keep every SBOM and make them searchable.
Stage 5: Match Against Advisories
What it is. Comparing the components in each SBOM with published vulnerability information, to find which products are affected.
How to do it. Collect advisories from the public vulnerability databases, from the open-source projects you use and from your vendors. Match on the package identifier and the version range. The simplest correct matcher treats a vulnerability as applying to versions from the version it was introduced up to, but excluding, the version that fixed it.
def vtuple(v):
return tuple(int(x) for x in v.split("."))
def affected(version, introduced, fixed):
return vtuple(introduced) <= vtuple(version) < vtuple(fixed)
advisories = [
{"id": "ADV-2026-0001", "pkg": "mbedtls", "introduced": "3.0.0", "fixed": "3.6.1"},
{"id": "ADV-2026-0002", "pkg": "zlib", "introduced": "1.2.0", "fixed": "1.3.1"},
]
hits = [(a["id"], c["name"], c["version"]) for a in advisories for c in comps
if c["name"] == a["pkg"] and affected(c["version"], a["introduced"], a["fixed"])]
print(hits) # [('ADV-2026-0001', 'mbedtls', '3.6.0')]
The sample finds one hit, the cryptographic library at 3.6.0, which is below the fixed version 3.6.1, and correctly ignores the compression library, which is already at its fixed version.
How it fails. Four ways. Version strings that are not simple numbers, such as release candidates and distribution suffixes, break naive comparisons. Vendors backport fixes without changing the version number, so a library that looks vulnerable is patched. Identifiers differ between databases, so the same library is missed. And the matcher runs only when someone remembers, so a new advisory goes unseen. Run the matching on a schedule, daily, across all stored SBOMs.
Check before moving on. Plant a known advisory against a component you ship and confirm that the pipeline reports it within a day.
Reading an Advisory: Version Ranges, Backports and Suffixes
The matcher in the previous stage is deliberately simple, and real advisories are not. Four traps appear within the first month of running it.
Trap | Example | What to do |
Versions that are not plain numbers | Release candidates, patch suffixes, distribution revisions | Normalise versions to a comparable form, and flag unparseable ones for review |
Backported fixes | A distribution patches a flaw without changing the upstream version | Check the vendor's own advisory before calling a component vulnerable |
Identifier mismatch | One database names the library differently from your list | Maintain an alias table, and match on more than one identifier |
Ranges with several fixed versions | A flaw fixed in 2.8.9 and in 3.1.2 on separate branches | Model each branch's range separately |
Treat the matcher's output as a list of candidates for review, not a verdict. A match tells you that someone should look, and the triage stage decides what to do. The cost of a missed match is a vulnerability you did not know about, and the cost of a false match is an hour of analysis, so tune for sensitivity and keep the review cheap.
A match is a question, not an answer. Make the review fast, the verdict written and the reason short, and the pipeline will keep its credibility with the people who must act on it.
Keep a record of how each advisory was handled, including the ones dismissed. When the same library reappears in a later advisory, the earlier reasoning saves time, and when an auditor asks how you handle vulnerabilities, the record is the evidence.
Stage 6: Triage With VEX
What it is. Deciding, for each match, whether your product is actually affected, and recording the decision.
How to do it. Most matches are not exploitable in your product. The library may be present but the vulnerable function never called, or the feature disabled at build time, or the vendor may have backported the fix. A VEX statement records the verdict and the reason, so that you do not repeat the analysis and your customers do not worry about alerts that do not apply to them.
Status | Meaning | What you do |
Not affected | The product contains the component but is not vulnerable, with a stated reason | Record the justification, notify customers if they ask |
Affected | The vulnerability applies | Assess exploitability, plan a fix, check whether reporting applies |
Fixed | A corrected version is released | Point to the release |
Under investigation | Analysis is in progress | Set a deadline for a verdict |
How it fails. Treating every match as an emergency, which exhausts the team and trains it to ignore alerts. Or the opposite, dismissing matches without a recorded reason, which leaves nothing to show an auditor or a customer. A short, written justification per decision is the discipline.
Check before moving on. Take five past matches and write the VEX status and reason for each. If you cannot justify a not-affected verdict in a sentence, it is not yet a verdict.
Stage 7: The Reporting Clock
What it is. The process that turns a confirmed, actively exploited vulnerability into the notices the regulation requires, within the time allowed.
How to do it. Write the runbook before you need it. Name who decides that a vulnerability counts as actively exploited, who files the notice, and who approves the text. Register on the reporting platform in advance and test a submission, because the first use under pressure is the worst time to find a login problem. Compute the deadlines from the moment of awareness and put them where everyone can see them.
import datetime as dt
def reporting_deadlines(aware_utc):
return {"early_warning_by": (aware_utc + dt.timedelta(hours=24)).isoformat(),
"notification_by": (aware_utc + dt.timedelta(hours=72)).isoformat()}
print(reporting_deadlines(dt.datetime(2026, 11, 12, 16, 30)))
# {'early_warning_by': '2026-11-13T16:30:00', 'notification_by': '2026-11-15T16:30:00'}
A notice at 16:30 on Thursday needs an early warning by 16:30 on Friday and a fuller notification by 16:30 on Sunday. Weekends do not stop the clock, so the rota must cover them. The deadlines for the final report depend on the case, and are in Article 14.
How it fails. The decision stalls over whether the vulnerability is really actively exploited. Decide in advance what evidence is enough, and who has authority to decide when it is unclear.
Check before moving on. Run a drill. Invent a notice on a Friday afternoon, answer the which-products question from the SBOMs, draft the early warning and time the exercise.
Stage 8: Delivering the Fix
What it is. Getting a corrected firmware image to the units in the field, securely, with a record that it happened.
How to do it. The reporting duty is only the start. A product that cannot be updated cannot be fixed, and the regulation's wider requirements include a defined support period and the delivery of security updates. Design updates to be signed, verified on the device, resilient to power loss and reversible when they fail, as our guide to secure boot and firmware signing sets out. Keep a record of which units run which version, so that you know how many remain exposed after a fix is released.
How it fails. The fix exists and cannot reach the units, either because the update path was never built or because customers have turned it off. Plan the delivery with the customer, including an offline route for units that never connect.
Open-Source Components: What You Owe and What You Can Ask
Most firmware is largely open-source code, and the regulation treats open-source software carefully. Open-source stewards, the foundations and communities that support projects on a sustained basis, have lighter obligations than manufacturers, and a manufacturer that integrates an open-source component into a commercial product remains responsible for that product.
In practice, three habits help. Record the exact upstream version and the patches you applied, because your build is not the same as the upstream release. Watch the project's security advisories directly, since they often precede the public databases. And when you find a flaw, report it to the project and contribute the fix, which reduces the work of maintaining a private fork.
Licences matter alongside security. The list you build for vulnerabilities also answers a licensing question, and the two uses support each other: a component list that is good enough for one is usually good enough for the other.
Vendors Who Will Not Give You a Hash
Vendor binaries are the hardest part of a firmware SBOM. A module maker supplies a radio stack as a compiled library. A processor vendor supplies drivers as binaries. Neither tells you what is inside.
Treat this as a procurement problem, not an engineering one. For new purchases, require a component list and a commitment to notify you of vulnerabilities, written into the purchase terms. For existing vendors, send a written request, record the answer, and escalate where it does not come. When a vendor cannot or will not provide the information, record that in your risk file, because it is itself information, and consider whether to continue using the part. Where a part is critical and the vendor silent, binary analysis can recover some component information, though with less certainty, and the SBOM should say that the data is derived.
A Composite: The Gateway With Four Copies of One Library
This is a representative composite, genericised, from several product-security reviews. The numbers are illustrative.
A company shipped a field gateway built on an embedded Linux image, with a radio module and a cloud client. A routine pipeline check, run for the first time across all released versions, produced an unwelcome result when a new advisory on a popular compression library arrived. The matcher found the library four times in one product.
The base image contained a system copy. The cloud client statically linked its own copy at a different version. The radio module's firmware, supplied as a binary update, bundled a third. And an open-source tool used for diagnostics, copied into the repository years earlier, carried a fourth, which no tool had ever listed.
The first three were found automatically. The fourth was found only because the team's rule for copied source files, a manifest entry for every third-party file, had been added the month before and a reviewer noticed the omission. Triage then took an afternoon: two copies were affected and exploitable through the cloud client's inputs, one was affected but unreachable, and the radio module's was affected in the vendor's stack, which required a written query. The vendor replied in four days that the vulnerable function was not compiled in, and the team recorded the answer as a not-affected statement with the vendor's message attached.
The fix shipped within two weeks and reached most units within a month. The lesson the team recorded was that the SBOM had been accurate about what it listed and silent about what it missed, and that the copied-file rule and the vendor questionnaire were as valuable as the generator.
When Customers and Auditors Ask for Your SBOM
Once you have a pipeline, requests will arrive: a customer's procurement team, a regulator, an assessment body or an integrator building your product into theirs. Decide your policy before the first one comes.
Share the SBOM in a standard format, per release, through a controlled channel and under a confidentiality agreement where the component list reveals sensitive design choices. Share the VEX statements with it, so that the recipient sees both what is inside and which known issues do not affect it. Do not share the working files, the internal tickets or the matching output. And keep the recipient list, so that when a vulnerability is confirmed you can tell the right customers.
Expect questions. Why does this component appear twice? What is the unknown entry? Why is a vendor binary listed without a hash? Answers prepared in advance, including an honest statement of what is not yet known and when it will be, build more trust than a perfect-looking list with silent gaps.
Offer a standing process, not a one-off file. A customer who receives a fresh SBOM with each release, with a named contact for questions and a stated response time for vulnerability notices, has what it needs for its own compliance work. That responsiveness is often what separates suppliers in a procurement decision.
Customers will not ask for the perfect SBOM. They will ask for a current one, from a supplier who answers questions and tells them first when something affects them.
Notes for Indian Manufacturers and Exporters
The regulation reaches any manufacturer that places products on the EU market, wherever the company is based. For an Indian company, that includes direct exporters, those selling through an EU distributor and those whose products are built into someone else's equipment sold in Europe.
Three practical consequences follow. European customers and distributors will ask for SBOMs and for evidence of a vulnerability-handling process, increasingly as a condition of purchase, well before the legal deadlines bite. A manufacturer's authorised representative or importer in the EU has duties of its own and will want the documents. And the same artefacts serve other markets: defence and critical-infrastructure customers in India and elsewhere are asking for component lists and update processes, so the pipeline pays back beyond Europe.
Take legal advice on your specific role and products, since classification, conformity routes and the treatment of each party depend on the details.
A Six-Week Build Plan
Week one: list every product and firmware version still in the field, and read Article 14 with your legal adviser.
Week two: write the manifest rule for third-party code, and send the component-list request to your vendors.
Week three: build the converter to CycloneDX and the minimum-elements gate, and run them on the latest release.
Week four: store every released SBOM in a searchable place, bind each to its firmware hash and sign it.
Week five: build the daily advisory matcher, plant a test advisory and write the VEX template.
Week six: write the reporting runbook, register on the platform, and run a Friday-afternoon drill.
Back to 16:30
The notice arrives. Someone opens a search box, types the name of the library, and gets a list of every released firmware version that contains it, with the supplier, the version and the hash. Within the hour the team knows which products are affected and which are not, and within the day it has an early warning drafted.
Everything in this guide exists to make that hour possible. Build the list at the source, check it on every release, keep every version, match it daily, write down the verdicts and rehearse the clock.
Related service: Certification and Compliance Support
Related reading: Secure Boot and Firmware Signing for Embedded Products: From Key Generation to Field Revocation and Air-Gapped AI: How to Run a Private LLM With No Internet Connection.
EurthTech is an engineering-first product design and embedded systems company with a development center in Hyderabad. To discuss a project, write to connect@eurthtech.com or see https://www.eurthtech.com/case-studies.




Comments