An SBOM (Software Bill of Materials) is an inventory listing every software component in a product, and it's becoming a legal requirement for embedded devices. The EU CRA mandates SBOM submission by 2027, the FDA already requires it for medical devices, and Yocto Scarthgap generates SBOMs out of the box.
This guide walks through generating SBOMs and scanning for CVE vulnerabilities using Yocto Scarthgap 5.0 LTS (BitBake 2.8) built-in tools. From the regulatory background to practical configuration and production workflows — everything in one article.
Why SBOM Is Becoming Mandatory for Embedded
An SBOM (Software Bill of Materials) is an inventory of all software components in a product. Which libraries are included, what versions they are, and what licenses they use — recorded in a machine-readable format.
You might think "we're too small for this to matter." But if you ship products to the EU, US, or automotive markets, your team is already affected.
EU Cyber Resilience Act (CRA)
The CRA (Regulation EU 2024/2847) entered into force on December 10, 2024. Full requirements apply from December 11, 2027 (vulnerability reporting obligations start September 11, 2026). It covers every business that manufactures, sells, or imports products with digital elements into the EU market. Japanese companies exporting to the EU are included.
Specific SBOM requirements:
- Create and maintain SBOMs in machine-readable format (SPDX / CycloneDX)
- Must be available for market surveillance authorities on request
- Penalties up to €15 million or 2.5% of global annual revenue
Medical Devices (FDA)
The US FDA has required SBOM submission since March 2023 under FD&C Act Section 524B (PATCH Act). SBOMs are mandatory for premarket submissions. The FDA can refuse to accept applications without one.
Automotive (UN-R155 / R156)
UN-R155 doesn't explicitly mandate SBOMs. However, operating a Cyber Security Management System (CSMS) requires knowing your software composition, and vulnerability management doesn't work without it. In Japan, UN-R155 type approval has been mandatory for new vehicle types since July 2022, and since July 2024 it applies to all newly manufactured vehicles including existing types.
Japan
No legal mandate yet. However, METI (Ministry of Economy, Trade and Industry) has published the "Guide to Introducing SBOMs" (version 2.0, August 2024), widely seen as groundwork for future legislation. In September 2025, Japan co-signed a 15-nation international SBOM guidance document led by CISA/NSA.
Generating SBOMs with create-spdx
The Yocto SBOM documentation covers the full create-spdx class. In Scarthgap, the default Poky distro configuration (poky.conf) includes INHERIT += "create-spdx", so it's enabled by default. A normal build automatically generates SBOMs in SPDX 2.2 format.
Check if it's enabled
bitbake-getvar -r core-image-minimal INHERIT
If the output includes create-spdx, you're set. If not, add the following to conf/local.conf.
# conf/local.conf — manually enable create-spdx (usually unnecessary)
INHERIT += "create-spdx"
Recommended settings
# conf/local.conf — recommended SBOM settings
SPDX_PRETTY = "1"
SPDX_PRETTY adds indentation to the JSON output, making it human-readable. File size increases, but it's worth it for debugging and review.
If you need source code traceability, add these as well.
# conf/local.conf — include source info in SBOM (optional)
SPDX_INCLUDE_SOURCES = "1"
SPDX_ARCHIVE_SOURCES = "1"
Build and check the output
Run a normal build.
bitbake core-image-minimal
After the build completes, the SBOM is at:
tmp/deploy/images/<MACHINE>/core-image-minimal-<MACHINE>.spdx.tar.zst
Extract and inspect the contents.
cd tmp/deploy/images/qemux86-64/
mkdir spdx-output && cd spdx-output
tar -I zstd -xf ../core-image-minimal-qemux86-64.spdx.tar.zst
ls *.spdx.json | head -10
Understanding the SBOM Output
After extraction, you'll find one SPDX file per recipe. Each file is in SPDX 2.2 JSON format.
File structure
spdx-output/
├── recipe-core-image-minimal.spdx.json ← entire image
├── recipe-busybox.spdx.json ← individual recipe
├── recipe-glibc.spdx.json
├── recipe-openssl.spdx.json
└── ...
Key JSON fields
Opening an individual SPDX file shows a structure like this (simplified).
{
"spdxVersion": "SPDX-2.2",
"dataLicense": "CC0-1.0",
"name": "recipe-openssl",
"packages": [
{
"name": "openssl",
"versionInfo": "3.2.1",
"supplier": "Organization: OpenSSL",
"downloadLocation": "https://www.openssl.org/source/openssl-3.2.1.tar.gz",
"licenseConcluded": "Apache-2.0",
"licenseDeclared": "Apache-2.0"
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Recipe",
"relationshipType": "GENERATES",
"relatedSpdxElement": "SPDXRef-Package-openssl"
}
]
}
The key fields for compliance:
- packages: Package name, version, license, and download location
- relationships: Dependency chain between recipes and packages
- licenseConcluded / licenseDeclared: License information (always reviewed in audits)
Scanning Vulnerabilities with cve-check
The cve-check class cross-references built packages against the NIST NVD (National Vulnerability Database) to detect known vulnerabilities. The Yocto vulnerability checking documentation covers the full configuration. Unlike create-spdx, it must be enabled manually.
Enable it
# conf/local.conf — enable CVE checking
INHERIT += "cve-check"
Check a specific recipe
bitbake -c cve_check openssl
Check the entire image
With INHERIT += "cve-check" set, a normal build generates CVE reports automatically.
bitbake core-image-minimal
Report locations
tmp/deploy/cve/
├── openssl ← per-recipe text
├── openssl_cve.json ← per-recipe JSON
└── ...
tmp/deploy/images/<MACHINE>/
├── core-image-minimal-<MACHINE>.cve ← image-wide text
└── core-image-minimal-<MACHINE>.cve.json ← image-wide JSON
Both text and JSON formats are generated by default.
Reading the report
The text report lists each CVE with its status.
PACKAGE NAME: openssl
PACKAGE VERSION: 3.2.1
CVE: CVE-2024-XXXXX
CVE STATUS: Patched
CVE SUMMARY: ...
CVE: CVE-2024-YYYYY
CVE STATUS: Unpatched
CVE SUMMARY: ...
There are three status values:
- Patched: Fixed by a Yocto patch
- Unpatched: Not fixed. Needs attention
- Ignored: Intentionally skipped (set via
CVE_STATUS)
Filtering CVE Report Noise
Enabling CVE checks can flood you with results. The Linux kernel alone has thousands of registered CVEs, and the vast majority don't apply to your specific architecture or kernel configuration.
Mark individual CVEs with CVE_STATUS
When a specific CVE doesn't apply to your platform, exclude it with CVE_STATUS.
# In a recipe or bbappend
CVE_STATUS[CVE-2024-12345] = "not-applicable-platform: Only affects Windows"
CVE_STATUS[CVE-2024-67890] = "not-applicable-config: Feature disabled in our config"
Status values are predefined in cve-check-map.conf. Common ones include not-applicable-platform, not-applicable-config, cpe-incorrect, and upstream-wontfix. The text after the colon documents the specific reason, making exclusions auditable. You can manage these settings in a bbappend without modifying the original recipe. If migrating from Kirkstone, you'll need to rewrite CVE_CHECK_IGNORE to CVE_STATUS — see the migration checklist.
Batch-manage with CVE_STATUS_GROUPS
When many CVEs share the same exclusion reason, group them for easier management.
# conf/local.conf or a custom inc file
CVE_STATUS_GROUPS = "CVE_STATUS_WIN CVE_STATUS_ARM32"
CVE_STATUS_WIN = "CVE-2024-11111 CVE-2024-22222 CVE-2024-33333"
CVE_STATUS_WIN[status] = "not-applicable-platform: Windows-only vulnerability"
CVE_STATUS_ARM32 = "CVE-2024-44444 CVE-2024-55555"
CVE_STATUS_ARM32[status] = "not-applicable-platform: ARM32-only, we target ARM64"
Limit scan scope by layer
Useful when you only want to check your own layer.
# conf/local.conf — check only your layer
CVE_CHECK_LAYER_INCLUDELIST = "meta-mylayer"
# or exclude specific layers
CVE_CHECK_LAYER_EXCLUDELIST = "meta-poky"
Use the official exclusion list
Poky ships with a pre-defined CVE exclusion list.
# conf/local.conf
include conf/distro/include/cve-extra-exclusions.inc
Production Workflow
Here's how to integrate SBOM and CVE checking into your daily build flow.
Recommended conf/local.conf
# conf/local.conf — SBOM + CVE recommended settings
# SBOM generation (enabled by default, but make it explicit)
INHERIT += "create-spdx"
SPDX_PRETTY = "1"
# CVE checking
INHERIT += "cve-check"
# Official CVE exclusion list
include conf/distro/include/cve-extra-exclusions.inc
Per-build flow
1. bitbake <image>
↓ automatic
2. SBOM generated → tmp/deploy/images/MACHINE/IMAGE.spdx.tar.zst
↓ automatic
3. CVE report generated → tmp/deploy/images/MACHINE/IMAGE.cve
↓
4. Review Unpatched CVEs
↓
5. Apply patches or exclude with CVE_STATUS (with documented reasons)
↓
6. Archive SBOM + CVE report in version control
Extract Unpatched CVEs
# Extract Unpatched entries from the text report
grep -B 3 "CVE STATUS: Unpatched" \
tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.cve
For debugging build issues that arise from patching CVEs, the build error debugging guide covers devshell and log analysis techniques.
EU CRA Compliance Checklist
| # | Requirement | How Yocto Handles It |
|---|---|---|
| 1 | Machine-readable SBOM | create-spdx (enabled by default) |
| 2 | Known vulnerability scanning | cve-check |
| 3 | Vulnerability response documentation | CVE_STATUS with reasons |
| 4 | Regular SBOM updates | Regenerate SBOM with each release build |
| 5 | Authority submission readiness | Archive .spdx.tar.zst as build artifacts |
FAQ
What's the difference between SPDX and CycloneDX?
Both are machine-readable SBOM formats. SPDX (ISO/IEC 5962:2021) originated from the Linux Foundation with a focus on license compliance. CycloneDX originated from OWASP with a focus on security and vulnerability tracking. Yocto's create-spdx generates SPDX format. Both formats are accepted by the EU CRA and FDA. If your downstream tooling or customers don't have a preference, SPDX is the natural choice for Yocto projects since it's generated natively.
Does cve-check require an internet connection?
Yes, for the initial NVD database download and subsequent updates. The NIST NVD API is rate-limited, so the first fetch can take 10-30 minutes. After that, only incremental updates are downloaded. For air-gapped environments, you can pre-fetch the NVD data and point cve-check to a local mirror.
How often should I regenerate SBOMs?
At minimum, regenerate with every release build. For products under EU CRA scope, you need to maintain "up-to-date" SBOMs, which means regenerating whenever you update a component. A practical approach: generate as part of your CI/CD pipeline on every release branch build, and archive alongside the image artifacts.
Can I use cve-check in CI without full builds?
Yes. Use bitbake -c cve_check <recipe> to scan individual recipes without building the full image. This is useful for quick checks in merge request pipelines. However, for comprehensive reports, the image-level scan (bitbake <image> with cve-check inherited) gives the complete picture.
What does CVE_STATUS "cpe-incorrect" mean?
CPE (Common Platform Enumeration) is how the NVD identifies affected software. Sometimes the NVD entry's CPE matches your package name but refers to a completely different software. For example, a CVE for "python-json" might match your custom json recipe but actually targets a different project. cpe-incorrect marks these false positives.
How do I handle CVEs in the Linux kernel?
The kernel is the noisiest source of CVE reports — thousands of entries, most irrelevant to your configuration. Start with Poky's cve-extra-exclusions.inc for known non-applicable entries. Then use CVE_STATUS_GROUPS to batch-exclude CVEs that only affect subsystems you've disabled. Document each exclusion with your kernel config rationale. The Yocto vulnerability docs have kernel-specific guidance.
Is SBOM generation adding significant build time?
Minimal. The create-spdx class runs as a post-processing step and adds only seconds to the build. It doesn't re-compile anything — it collects metadata that BitBake already tracks. cve-check has a larger initial cost (NVD download), but subsequent builds add only a few minutes for the database diff and scan.
Wrapping Up
SBOM and CVE management is no longer a "nice to have." The EU CRA deadline in 2027, the existing FDA mandate, the practical requirements of UN-R155 — regulation is closing in.
The good news: compliance with Yocto Scarthgap is simpler than you might think.
| Task | Method | Effort |
|---|---|---|
| SBOM generation | create-spdx (enabled by default) | Near zero |
| CVE scanning | INHERIT += "cve-check" | One line in conf/local.conf |
| Noise filtering | CVE_STATUS + documented reasons | One-time setup effort |
| Operations | Archive SBOM + CVE report per build | Automate via CI/CD |
SBOMs are already being generated by default. CVE checking takes one line to enable. The first step toward regulatory compliance is easier than you think.
Related articles:
- SWUpdate vs Mender vs RAUC: A Yocto OTA Comparison
- Kirkstone to Scarthgap Migration: The Complete Checklist
- Yocto Build Errors: Task-by-Task Debugging Guide
- Getting Started with Yocto: Build Your First Linux Image
- How to Write Yocto Recipes: A .bb File Guide
- Yocto bbappend: A Practical Guide with 5 Patterns
- 10x Faster Yocto Builds