Embedded Linux DevOps

SBOMs and CVE Checks in Yocto Builds for the EU Cyber Resilience Act

How to generate an SBOM and run CVE checks on every Yocto build, keep both tied to the firmware you actually shipped, and answer a 24-hour reporting question without a scramble.

By Subeesh Sivanandan, Founder & CEO, Stonetusker Systems · Updated September 2026 · Est. reading time: 13 minutes

Executive brief

  • A Yocto build can generate a machine-readable SPDX SBOM for every image, and a CVE scan of that SBOM shows which shipped components have known vulnerabilities. Since 11 September 2026 the EU Cyber Resilience Act (CRA) has required manufacturers to report actively exploited vulnerabilities within 24 hours of becoming aware, and from 11 December 2027 the SBOM itself is a legal requirement. The practical goal is to produce both artifacts in CI on every build and keep them tied to the exact firmware version in the field.
  • The legal minimum for the SBOM is a commonly used, machine-readable format that covers at least top-level dependencies. It sits in your technical documentation and goes to authorities on request, not to the public.
  • Yocto 6.0 (Wrynose, an LTS release) removed the old cve-check class and replaced it with sbom-cve-check. Teams on Scarthgap or earlier still use cve-check, so the right setup depends on your release.
  • A scan at build time isn't enough. New CVEs land against images you shipped months ago, so archive every release's SBOM and rescan the archive on a schedule.
  • Triage decisions (patched, not affected, ignored) belong in recipe metadata under code review, not in a spreadsheet.
  • An SBOM and a CVE report support the vulnerability handling duties. They don't make a product compliant by themselves.

What the CRA Actually Asks of a Yocto Team

Two dates matter, and one has already passed.

Article 14 reporting has applied since 11 September 2026. If you learn that someone is actively exploiting a vulnerability in a product you've placed on the EU market, you owe an early warning within 24 hours and a fuller notification within 72 hours, both filed through ENISA's Single Reporting Platform. A final report follows within 14 days after a corrective measure becomes available. Products already in the field are covered, not just new ones. The European Commission's reporting page lays out the stages.

The second date is 11 December 2027, when the main obligations apply, and the SBOM requirement lives there. Annex I, Part II asks manufacturers 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 the very least the top-level dependencies. The regulation doesn't name a format. SPDX and CycloneDX are what people mean by "commonly used," and Yocto produces SPDX natively.

A few details get lost in the rush. The SBOM goes into your technical documentation and reaches market surveillance authorities on request. It isn't published. You keep that documentation for at least ten years after placing the product on the market, or for the support period if that's longer, and the support period is five years at minimum unless the product is expected to be used for less. Fines for breaching the reporting duty can reach 15 million euros or 2.5 percent of worldwide turnover, whichever is higher, with the exact amount left to each member state's enforcement.

Operational test

A CVE was published this morning against OpenSSL or the Linux kernel. Can someone on your team say, within an hour and without logging into a build machine, which of your shipped firmware versions contain the affected version?

What Yocto Gives You Out of the Box

Yocto has a real advantage here. The build system compiles or assembles every package in the image, so it can describe the image from the inside. A scanner that inspects a finished filesystem is guessing. The build knows.

The create-spdx class does the work, and the distro setup inherits it by default. The image SBOM lands in the deploy directory, under tmp/deploy/images/<machine>/, as an SPDX JSON file. Since Styhead (5.1) that file is SPDX 3.0. Scarthgap defaults to SPDX 2.2, and Wrynose dropped 2.2 completely. The Yocto SBOM documentation covers the class and its options.

The defaults are a decent start. A few options make the file far more useful for CRA work:

  • SPDX_INCLUDE_COMPILED_SOURCES, which records the source files that were actually compiled into a package. It matters most for the kernel, as you'll see below.
  • SPDX_INCLUDE_KERNEL_CONFIG and SPDX_INCLUDE_PACKAGECONFIG, so the document says which options were switched on.
  • Supplier fields for the image and for individual packages (SPDX_IMAGE_SUPPLIER, SPDX_PACKAGE_SUPPLIER). Assessors ask who supplies what.
  • Package URLs for Rust, Go, Python, npm and Perl content, filled in by the language classes.
  • SPDX_INCLUDE_BITBAKE_PARENT_BUILD, SPDX_INVOKED_BY and SPDX_ON_BEHALF_OF, which record which CI agent ran the build and for whom.

Which tooling you get depends on the release you're on, and this is where teams trip.

Yocto releaseSPDX outputCVE toolingNotes
Scarthgap 5.0 (LTS)SPDX 2.2 by default. SPDX 3.0 is available through the create-spdx-3.0 class on point releases that carry the backport.cve-check class. sbom-cve-check is usable from the scarthgap branch.Check your point release before you rely on SPDX 3.0. SPDX 2.2 carries very little CVE status information.
Styhead 5.1 to Whinlatter 5.3SPDX 3.0 by defaultcve-check still present. sbom-cve-check available through the meta-sbom-cve-check layer.Non-LTS releases, so a poor base for a five-year support promise.
Wrynose 6.0 (LTS, May 2026)SPDX 3.0 onlysbom-cve-check class. cve-check removed.Supported until at least April 2030.
Operational test

Open the deploy directory of your latest release build. Is there an SPDX file in it that you'd hand to an assessor today, and does its format version match what you believe you ship?

CVE Checks: cve-check, sbom-cve-check and the 6.0 Change

One change trips up anyone following older guides. Yocto 6.0 (Wrynose), the LTS release from May 2026, removed the cve-check class. Its replacement is sbom-cve-check, an external tool written by Bootlin that reads the SPDX SBOM and matches components against CVE databases. The maintainers' reasoning, in short: cve-check was hard to use on real projects, and they wanted a scanner that runs during the build and also later, against an image that already exists. That second property is the one the CRA cares about. The 6.0 migration notes and the sbom-cve-check documentation have the details.

On Wrynose you switch it on with one line in a configuration file, OE_FRAGMENTS += "core/yocto/sbom-cve-check". After an image build you get two files in the deploy directory. One is a JSON report ending in .sbom-cve-check.yocto.json, in the same shape the old class produced. The other is your SPDX file, annotated with the vulnerable CVEs. Each CVE ends up as Patched, Unpatched or Ignored.

Scarthgap teams keep using INHERIT += "cve-check". Get an NVD API key and pass it in through NVDCVE_API_KEY, because without one the database fetch is throttled and CI jobs crawl. It's also worth trying sbom-cve-check on the scarthgap branch before you migrate, so the switch to 6.0 holds no surprises.

Triage as code

Every scanner produces noise. Yocto gives you places to record decisions where they can be reviewed. A CVE_STATUS flag with a reason maps a CVE to Ignored. CVE_PRODUCT fixes a wrong recipe-to-NVD-product mapping. A backported patch that carries a CVE: tag in its header flips the finding to Patched. All of it sits in your layers, so it goes through pull requests and Git history. That's the audit trail an assessor wants, and a spreadsheet can't give it to them.

The kernel problem

Since the Linux kernel became a CVE Numbering Authority, kernel CVEs have multiplied, and a raw report against a vendor kernel can bury the findings that matter. OE-Core ships a script, improve_kernel_cve_report.py, that combines the kernel CNA data with the list of files actually compiled into your kernel and reclassifies the CVEs. The Yocto manual says it cuts false positives by 70 to 80 percent. It needs debug information in the kernel build and SPDX_INCLUDE_COMPILED_SOURCES set for your kernel recipe. See the Yocto security manual for the exact steps.

Operational test

Pick five Ignored CVEs from your last report. For each one, can an engineer point to the written reason, the author, and the review it passed?

The 24-Hour Test: Can You Query What You Shipped?

The reporting clock starts when you become aware of active exploitation. It doesn't wait for your next scheduled scan.

Mandiant's M-Trends 2026 report puts the mean time to exploit at an estimated minus seven days for 2025, which means attacks on average begin before a patch exists. A 30-day patching SLA doesn't answer that. What answers it is an inventory you can query in minutes: which firmware versions, on which device models, contain the affected component at an affected version.

So the SBOM has to be stored per release, indexed, and kept somewhere other than the build machine that made it. A build server that gets wiped every quarter makes a poor system of record. A shared drive full of files named final_v2 is worse.

Then there are the people. Somebody owns the filing on the Single Reporting Platform. Somebody knows which national CSIRT receives it (the one in the member state where you make your main cybersecurity decisions). Somebody can tell affected customers, since Article 14 also requires you to inform users. Rehearse it once with an invented CVE before a real one shows up.

Operational test

Run a fire drill this week. Pick a component that's in your image, pretend it's being exploited, and time how long it takes to produce the list of affected firmware versions. If the honest answer is "about a day," the 24-hour clock has already beaten you.

Playbook: SBOM and CVE Gates in Your CI/CD Pipeline

None of this works as a manual step before release. It has to be part of the pipeline that builds the image, and if you don't have that pipeline yet, our notes on automating Yocto and Buildroot builds and on running Yocto in the cloud cover the groundwork. Here's the sequence we'd put in place.

  1. Pick the Yocto release with the support window in mind

    The CRA support period is five years at minimum for most products, and a Yocto LTS release is supported for four. Wrynose runs to at least April 2030. Decide now whether you'll carry the layer set yourself in year five, because your SBOM and scanning setup has to keep working after upstream stops.

  2. Generate SPDX 3.0 on every build

    Confirm that create-spdx is active and the output is SPDX 3.0 (on Scarthgap, switch to create-spdx-3.0). Set the supplier variables and the compiled-source options for your kernel. Build the image in CI and never from a laptop.

  3. Run the CVE check inside the image build

    On Wrynose, add the sbom-cve-check fragment. On older releases, enable cve-check and pass NVDCVE_API_KEY through BB_ENV_PASSTHROUGH_ADDITIONS so it reaches BitBake as a CI secret. Persist the CVE database between runs so CI isn't refetching it on every build.

  4. Keep the files with the firmware

    For each release, store the SPDX file, the CVE report and the VEX manifest (if you produce one) beside the signed image, filed under the firmware version and image hash. Make the storage write-once. Retention should match your technical documentation: at least ten years, longer if the support period is longer.

  5. Rescan the archive every night

    Run the scanner against every SBOM for a release you still support, using fresh CVE data. This is the job that catches a CVE published in March against an image shipped last year. The sbom-cve-check tool runs standalone on an SBOM file, which keeps this cheap.

  6. Treat triage decisions as code

    Ignored, patched and not-affected calls go into CVE_STATUS, CVE tags in patch headers, and CVE_PRODUCT. Reviews happen in pull requests. Nobody edits a spreadsheet.

  7. Gate the release

    Fail the release job when an image has unpatched, high severity findings with no recorded decision. Set the threshold with your product security lead instead of guessing. Sign the image after the gate, not before.

  8. Wire findings to the reporting runbook

    A nightly finding that matches a CVE known to be exploited should page a person who can start the 24-hour process. Point that alert at whoever owns the Single Reporting Platform account.

Wrynose (6.0): conf/local.conf

# Enable sbom-cve-check with the recommended settings
OE_FRAGMENTS += "core/yocto/sbom-cve-check"
SBOM_CVE_CHECK_SHOW_WARNINGS = "1"

# Record compiled kernel sources so kernel CVEs can be filtered
# (linux-yocto shown, adjust the name for your own kernel recipe)
SPDX_INCLUDE_COMPILED_SOURCES:pn-linux-yocto = "1"
KERNEL_EXTRA_FEATURES:append = " features/debug/debug-kernel.scc"

Scarthgap (5.0): conf/local.conf, on a point release with the SPDX 3.0 backport

INHERIT += "cve-check"
INHERIT:remove = "create-spdx"
INHERIT += "create-spdx-3.0"

Nightly rescan of archived releases (any CI runner)

#!/usr/bin/env bash
# Rescan every archived release SBOM against current CVE data.
set -euo pipefail

ARCHIVE_DIR="$1"        # one folder per firmware version
OUT_DIR="${2:-reports}"
mkdir -p "$OUT_DIR"

for release_dir in "$ARCHIVE_DIR"/*/; do
  version=$(basename "$release_dir")
  # drop --yocto-vex-manifest if you don't archive a VEX manifest
  sbom-cve-check \
    --sbom-path "$release_dir/image.spdx.json" \
    --yocto-vex-manifest "$release_dir/image.vex.json" \
    --export-filter-vulnerable \
    --export-type csv \
    --export-path "$OUT_DIR/$version.csv"
done

The file names in that script assume you renamed the artifacts when you archived them. Feed each CSV into your ticketing or alerting, and fail the job if a release you still support has open findings.

Where Yocto SBOMs Fall Short

Yocto's SBOM is good. It isn't complete, and assessors and attackers both go looking for the gaps.

  • Prebuilt binaries. Vendor blobs, closed-source kernel modules and binary SDK libraries appear only if someone described them properly in a recipe. If a BSP vendor hands over a binary and a shrug, your SBOM has a hole in the one place you can't inspect.
  • Vendored language dependencies. Go, Rust, Python and Node packages pulled in by a recipe are only as visible as the fetch mechanism allows. The package URL support in the language classes helps. Worth knowing too: Wrynose disabled the npm and npmsw fetchers over security concerns and lack of maintenance, so images with Node content need a plan.
  • Name matching. Mapping recipe names to NVD products produces false positives and false negatives. Someone has to own the CVE_PRODUCT fixes.
  • Format. If a customer or authority asks for CycloneDX, converting from SPDX is possible but loses information. Generate what you'll be held to at the source. Germany's BSI guideline TR-03183 (part 2, version 2.1.0) accepts CycloneDX 1.6 or higher, or SPDX 3.0.1 or higher. It's non-binding, but it's the most detailed SBOM specification an EU authority has published, and SPDX 2.2 output doesn't meet it.
  • Rebuilds. An SBOM from a later rebuild of the same tag isn't the SBOM of what you shipped. Archive the one produced by the build that made the released binary.

What This Covers for the CRA, and What It Doesn't

An SBOM plus a CVE workflow serves Annex I, Part II of the CRA, the vulnerability handling requirements. It shows you know what's in the product, and it feeds the technical documentation and the reporting process. That's a real chunk of the work.

It leaves a lot untouched. Secure-by-default configuration, secure boot and a signed update path, a coordinated vulnerability disclosure policy, the conformity assessment route for your product category, and CE marking all sit outside it. Scope matters too. Products such as medical devices and vehicles fall under their own EU rules, and some products are classed as important or critical, which changes how conformity is assessed. This is engineering guidance, not legal advice, so have counsel confirm where your product lands.

For primary sources, read Regulation (EU) 2024/2847 itself and ENISA's Single Reporting Platform page. We also covered the software composition, SBOM and open-source licence side of the CRA in our newsletter.

Anti-Patterns That Fail the First Audit or the First Incident

Anti-patternWhat breaksFix
SBOM built once at release from the source repositoryMisses packages the build pulls in, and goes stale the day after release.Generate it in CI from the build that produced the binary, on every build.
CVE report read only at build timeNew CVEs against shipped images go unnoticed for months.Nightly rescan of the archived SBOMs.
Triage kept in a spreadsheetNo review trail, and it drifts away from the code.Record decisions with CVE_STATUS, CVE patch tags and CVE_PRODUCT, reviewed in pull requests.
Trusting raw scanner outputKernel noise and name mismatches bury the real findings.Tune product mappings. Use the kernel CVE script with compiled-source data.
Stopping at SPDX 2.2Fails the BSI baseline and carries little CVE status.Move to SPDX 3.0.
One SBOM for the whole product lineCan't answer which firmware version is affected.One SBOM per firmware version and device model.

Concepts covered

  • Yocto Project
  • SBOM
  • SPDX 3.0
  • CVE scanning
  • sbom-cve-check
  • cve-check
  • EU Cyber Resilience Act
  • Article 14 reporting
  • CI/CD
  • Embedded Linux
  • VEX
  • Technical documentation

Frequently Asked Questions

Does the CRA require an SBOM for my Yocto image?

Yes, if your product is in scope. From 11 December 2027, manufacturers must document the components in their products, including through an SBOM in a commonly used, machine-readable format that covers at least the top-level dependencies. The SBOM goes into your technical documentation and to authorities on request. Some products, such as medical devices and vehicles, fall under their own EU rules instead, so confirm scope with legal counsel.

Should I use SPDX or CycloneDX for a Yocto build?

Either is accepted, because the CRA only asks for a commonly used, machine-readable format. Yocto generates SPDX natively, so most Yocto teams stay on SPDX. Converting between the two formats loses information, so generate the one you'll be held to at the source. If you follow the German BSI guideline, use SPDX 3.0.1 or higher, or CycloneDX 1.6 or higher.

What replaced the cve-check class in Yocto?

Yocto 6.0 (Wrynose) removed cve-check and replaced it with sbom-cve-check, an external tool from Bootlin that reads your SPDX SBOM and matches it against CVE databases. You turn it on by adding core/yocto/sbom-cve-check to OE_FRAGMENTS. Releases before 6.0 still use cve-check.

How often should I rescan shipped firmware for new CVEs?

At least once a day for every firmware version you still support. New CVEs get published against components in images you shipped long ago, and the 24-hour reporting clock starts when you become aware of active exploitation. A nightly scan of your archived SBOMs is cheap compared with learning about a problem from a customer.

Do I have to report every CVE to ENISA?

No. The reporting duty covers actively exploited vulnerabilities and severe incidents that affect product security. A CVE showing up in a report doesn't trigger it, but active exploitation does. Other vulnerabilities still have to be handled without delay, including through security updates.

How long do I have to keep SBOMs?

Keep them with your technical documentation for at least ten years after the product is placed on the market, or for the support period if that is longer. The support period is at least five years unless the product is expected to be used for less. Store the SBOM from the exact build you released, filed under the firmware version.

Need this running before your next release?

If your Yocto builds don't yet produce an SBOM, scan it, archive it per firmware version and answer the 24-hour question, that's a pipeline we build. We start with a short paid pilot on your own build, then extend it across your device lines in a fixed-scope 90-day engagement. No retainer.

Talk to us about your Yocto pipeline

About the author

Subeesh Sivanandan is Founder & CEO of Stonetusker Systems. He has spent more than 26 years across platform engineering, DevOps, CI/CD, release engineering and embedded Linux, including Yocto and Buildroot delivery for regulated products.

Connect on LinkedIn