[{"data":1,"prerenderedAt":2194},["ShallowReactive",2],{"blog-zephyr-sbom-cra-compliance":3},{"id":4,"title":5,"body":6,"date":2178,"description":2179,"extension":2180,"image":2181,"keywords":2182,"meta":2188,"navigation":165,"path":2189,"readTime":2190,"seo":2191,"stem":2192,"__hash__":2193},"blog/blog/zephyr-sbom-cra-compliance.md","From west spdx to a CRA-Compliant Zephyr SBOM",{"type":7,"value":8,"toc":2147},"minimark",[9,18,21,24,41,49,55,61,77,82,102,106,112,192,198,205,265,271,289,292,296,301,304,313,317,324,327,330,334,337,342,356,360,366,370,381,384,388,391,395,398,496,503,526,530,537,545,548,811,818,822,825,832,838,841,872,875,879,886,889,1018,1021,1050,1053,1072,1078,1082,1091,1094,1139,1142,1157,1164,1168,1171,1251,1256,1267,1272,1286,1289,1293,1296,1300,1320,1323,1326,1359,1363,1366,1369,1389,1397,1401,1404,1728,1731,1739,1745,1830,1836,1843,1848,1875,1880,1907,1912,1951,1956,1985,1990,2020,2025,2052,2064,2067,2073,2077,2143],[10,11,12,13,17],"p",{},"Zephyr has a built-in SBOM generation command: ",[14,15,16],"code",{},"west spdx",". Run it after a build, and you'll get four SPDX files describing your application, Zephyr kernel, and build environment. It's one of the few RTOSes with any native SBOM tooling at all.",[10,19,20],{},"The problem is that the output is nearly useless for CRA compliance out of the box.",[10,22,23],{},"The generated SPDX files lack CPE and PURL identifiers, so vulnerability scanners can't match components to known CVEs. Subsystem components like the Bluetooth stack, MQTT library, and cJSON parser don't appear as discrete packages — they're compiled into the Zephyr kernel blob. Vendor HAL binary blobs are opaque. And the output is SPDX-only, with no CycloneDX option.",[10,25,26,27,29,30,35,36,40],{},"This tutorial covers what ",[14,28,16],{}," gives you, what it doesn't, and the step-by-step enrichment workflow to produce an SBOM that actually satisfies CRA Annex I Part II. For broader SBOM context, see our ",[31,32,34],"a",{"href":33},"/blog/cra-sbom-firmware/","SBOM guide for firmware",". For the full Zephyr CRA picture, see our ",[31,37,39],{"href":38},"/blog/cra-compliance-zephyr-rtos/","Zephyr CRA compliance guide",".",[42,43,45,46,48],"h2",{"id":44},"what-west-spdx-gives-you-and-what-it-doesnt","What ",[14,47,16],{}," Gives You (and What It Doesn't)",[10,50,51,52,54],{},"The ",[14,53,16],{}," command was added in Zephyr 3.x. It hooks into the CMake build system and generates SPDX 2.3 documents based on the source files and libraries that were actually compiled.",[10,56,57],{},[58,59,60],"strong",{},"What it does well:",[62,63,64,68,71,74],"ul",{},[65,66,67],"li",{},"Enumerates source files that went into the build (not the full manifest — only what was compiled)",[65,69,70],{},"Produces valid SPDX 2.3 documents",[65,72,73],{},"Generates four separate SPDX files: application, Zephyr kernel, build environment, and an overall document",[65,75,76],{},"Uses build-system data, so it reflects your actual configuration (Kconfig-dependent)",[10,78,79],{},[58,80,81],{},"What it doesn't do:",[62,83,84,87,90,93,96,99],{},[65,85,86],{},"No CPE or PURL identifiers on any package — vulnerability scanners get nothing to match against",[65,88,89],{},"No version information derived from upstream sources — packages are identified by local path only",[65,91,92],{},"Subsystem components (Bluetooth, networking, USB) are rolled into the Zephyr kernel package, not listed individually",[65,94,95],{},"Vendor HAL modules (hal_stm32, hal_nordic, hal_nxp) appear as packages but with no upstream version or vulnerability identifiers",[65,97,98],{},"SPDX output only — no CycloneDX option",[65,100,101],{},"MCUboot (if used) is a separate build and isn't captured in the application's SPDX output",[42,103,105],{"id":104},"step-by-step-generating-spdx-from-a-zephyr-build","Step by Step: Generating SPDX from a Zephyr Build",[10,107,108,109,111],{},"Start with a clean build. The ",[14,110,16],{}," command operates on the build directory, so it needs a completed build to analyse.",[113,114,119],"pre",{"className":115,"code":116,"language":117,"meta":118,"style":118},"language-bash shiki shiki-themes github-light github-dark","# Clean build for your board\nwest build -p always -b nrf52840dk/nrf52840 -- -DCONFIG_BOOTLOADER_MCUBOOT=y\n\n# Generate SPDX documents\nwest spdx -d build -n \"https://your-company.com/spdx\"\n","bash","",[14,120,121,130,160,167,173],{"__ignoreMap":118},[122,123,126],"span",{"class":124,"line":125},"line",1,[122,127,129],{"class":128},"sJ8bj","# Clean build for your board\n",[122,131,133,137,141,145,148,151,154,157],{"class":124,"line":132},2,[122,134,136],{"class":135},"sScJk","west",[122,138,140],{"class":139},"sZZnC"," build",[122,142,144],{"class":143},"sj4cs"," -p",[122,146,147],{"class":139}," always",[122,149,150],{"class":143}," -b",[122,152,153],{"class":139}," nrf52840dk/nrf52840",[122,155,156],{"class":143}," --",[122,158,159],{"class":143}," -DCONFIG_BOOTLOADER_MCUBOOT=y\n",[122,161,163],{"class":124,"line":162},3,[122,164,166],{"emptyLinePlaceholder":165},true,"\n",[122,168,170],{"class":124,"line":169},4,[122,171,172],{"class":128},"# Generate SPDX documents\n",[122,174,176,178,181,184,186,189],{"class":124,"line":175},5,[122,177,136],{"class":135},[122,179,180],{"class":139}," spdx",[122,182,183],{"class":143}," -d",[122,185,140],{"class":139},[122,187,188],{"class":143}," -n",[122,190,191],{"class":139}," \"https://your-company.com/spdx\"\n",[10,193,51,194,197],{},[14,195,196],{},"-n"," flag sets the SPDX namespace URI. Use your company's domain — this becomes the document namespace in the SPDX output.",[10,199,200,201,204],{},"This produces four files in ",[14,202,203],{},"build/spdx/",":",[206,207,208,221],"table",{},[209,210,211],"thead",{},[212,213,214,218],"tr",{},[215,216,217],"th",{},"File",[215,219,220],{},"Contents",[222,223,224,235,245,255],"tbody",{},[212,225,226,232],{},[227,228,229],"td",{},[14,230,231],{},"app.spdx",[227,233,234],{},"Your application source files and libraries",[212,236,237,242],{},[227,238,239],{},[14,240,241],{},"zephyr.spdx",[227,243,244],{},"Zephyr kernel and all compiled subsystems",[212,246,247,252],{},[227,248,249],{},[14,250,251],{},"build.spdx",[227,253,254],{},"Build tools (compiler, linker)",[212,256,257,262],{},[227,258,259],{},[14,260,261],{},"sdk.spdx",[227,263,264],{},"Relationships between the above documents",[10,266,267,268,270],{},"Open ",[14,269,241],{}," and you'll see packages for Zephyr modules that were compiled. But try running a vulnerability scanner against it:",[113,272,274],{"className":115,"code":273,"language":117,"meta":118,"style":118},"# This will find almost nothing — no CPE/PURL to match against\ngrype sbom:build/spdx/zephyr.spdx\n",[14,275,276,281],{"__ignoreMap":118},[122,277,278],{"class":124,"line":125},[122,279,280],{"class":128},"# This will find almost nothing — no CPE/PURL to match against\n",[122,282,283,286],{"class":124,"line":132},[122,284,285],{"class":135},"grype",[122,287,288],{"class":139}," sbom:build/spdx/zephyr.spdx\n",[10,290,291],{},"Zero or near-zero results. Not because your firmware has no vulnerabilities, but because the scanner has no identifiers to look up.",[42,293,295],{"id":294},"the-5-gaps-that-make-raw-output-useless-for-cra","The 5 Gaps That Make Raw Output Useless for CRA",[297,298,300],"h3",{"id":299},"gap-1-no-cpepurl-identifiers","Gap 1: No CPE/PURL Identifiers",[10,302,303],{},"This is the critical gap. The NTIA minimum SBOM elements (referenced by ENISA guidance) require \"other unique identifiers\" — specifically CPE or PURL — so that consumers of the SBOM can correlate components to vulnerability databases like NVD and OSV.",[10,305,51,306,308,309,312],{},[14,307,16],{}," output identifies packages by their local filesystem path (",[14,310,311],{},"/home/user/zephyrproject/modules/crypto/mbedtls","). No vulnerability scanner can do anything with this.",[297,314,316],{"id":315},"gap-2-invisible-subsystem-components","Gap 2: Invisible Subsystem Components",[10,318,319,320,323],{},"When you enable Bluetooth in Zephyr (",[14,321,322],{},"CONFIG_BT=y","), the Bluetooth stack source files get compiled into the Zephyr kernel. In the SPDX output, these files appear under the main Zephyr package — there's no separate \"Bluetooth Host Stack\" package entry with its own version and identifiers.",[10,325,326],{},"The same applies to the networking stack (TCP/IP, MQTT, CoAP), USB, cJSON, and other subsystems. They're compiled-in library code, not discrete packages from the build system's perspective.",[10,328,329],{},"For CRA compliance, these are distinct components with distinct vulnerability histories and they need their own SBOM entries.",[297,331,333],{"id":332},"gap-3-vendor-hal-binary-blobs","Gap 3: Vendor HAL Binary Blobs",[10,335,336],{},"Zephyr's HAL modules (hal_stm32, hal_nordic, hal_nxp, hal_espressif) contain vendor-provided code that often includes pre-compiled binary blobs — particularly for radio stacks (BLE SoftDevice on Nordic, WiFi firmware on ESP32).",[10,338,51,339,341],{},[14,340,16],{}," output will list the HAL module as a package, but:",[62,343,344,347,350,353],{},[65,345,346],{},"The binary blob contents are completely opaque",[65,348,349],{},"No sub-components are enumerated",[65,351,352],{},"No vendor-provided SBOM is included",[65,354,355],{},"Vulnerability status of the binary code is unknown",[297,357,359],{"id":358},"gap-4-spdx-only-no-cyclonedx","Gap 4: SPDX Only — No CycloneDX",[10,361,362,363,365],{},"Some organisations and tools prefer CycloneDX, and some supply chain requirements specify it. The ",[14,364,16],{}," command only outputs SPDX. Converting SPDX to CycloneDX is possible but lossy.",[297,367,369],{"id":368},"gap-5-mcuboot-is-a-separate-build","Gap 5: MCUboot Is a Separate Build",[10,371,372,373,377,378,380],{},"If you're using MCUboot (and for CRA secure boot compliance, you should be — see our ",[31,374,376],{"href":375},"/blog/cra-secure-boot-firmware-signing/","secure boot guide","), it's built as a separate application. Running ",[14,379,16],{}," on your main application build doesn't capture MCUboot's dependencies. You need to generate and merge a separate SBOM for the bootloader.",[10,382,383],{},"Additionally, MCUboot has limited NVD coverage. Searching the NVD for \"MCUboot\" returns few or no results, even though security issues have been found and fixed. Your vulnerability scanning process needs to account for this.",[42,385,387],{"id":386},"enriching-the-sbom-adding-cpepurl-identifiers","Enriching the SBOM: Adding CPE/PURL Identifiers",[10,389,390],{},"This is the hardest part and where no existing tooling helps you. You need to manually map each component to its upstream identity and add CPE/PURL identifiers.",[297,392,394],{"id":393},"identifying-your-components","Identifying Your Components",[10,396,397],{},"First, extract the package list from the SPDX output and map each to its upstream source:",[206,399,400,416],{},[209,401,402],{},[212,403,404,407,410,413],{},[215,405,406],{},"Local package (from west spdx)",[215,408,409],{},"Upstream component",[215,411,412],{},"CPE",[215,414,415],{},"PURL",[222,417,418,438,458,478],{},[212,419,420,425,428,433],{},[227,421,422],{},[14,423,424],{},"modules/crypto/mbedtls",[227,426,427],{},"Mbed TLS 3.6.2",[227,429,430],{},[14,431,432],{},"cpe:2.3:a:arm:mbed_tls:3.6.2:*:*:*:*:*:*:*",[227,434,435],{},[14,436,437],{},"pkg:github/Mbed-TLS/mbedtls@v3.6.2",[212,439,440,445,448,453],{},[227,441,442],{},[14,443,444],{},"modules/lib/cjson",[227,446,447],{},"cJSON 1.7.17",[227,449,450],{},[14,451,452],{},"cpe:2.3:a:cjson_project:cjson:1.7.17:*:*:*:*:*:*:*",[227,454,455],{},[14,456,457],{},"pkg:github/DaveGamble/cJSON@v1.7.17",[212,459,460,465,468,473],{},[227,461,462],{},[14,463,464],{},"bootloader/mcuboot",[227,466,467],{},"MCUboot 2.1.0",[227,469,470],{},[14,471,472],{},"cpe:2.3:a:mcuboot:mcuboot:2.1.0:*:*:*:*:*:*:*",[227,474,475],{},[14,476,477],{},"pkg:github/mcu-tools/mcuboot@v2.1.0",[212,479,480,485,488,491],{},[227,481,482],{},[14,483,484],{},"modules/hal/nordic",[227,486,487],{},"nRF Connect SDK HAL",[227,489,490],{},"—",[227,492,493],{},[14,494,495],{},"pkg:github/zephyrproject-rtos/hal_nordic@\u003Ccommit>",[10,497,498,499,502],{},"Get the exact versions from your ",[14,500,501],{},"west.yml"," manifest:",[113,504,506],{"className":115,"code":505,"language":117,"meta":118,"style":118},"# Check exact module revisions pinned in your manifest\nwest list --format \"{name} {revision} {url}\"\n",[14,507,508,513],{"__ignoreMap":118},[122,509,510],{"class":124,"line":125},[122,511,512],{"class":128},"# Check exact module revisions pinned in your manifest\n",[122,514,515,517,520,523],{"class":124,"line":132},[122,516,136],{"class":135},[122,518,519],{"class":139}," list",[122,521,522],{"class":143}," --format",[122,524,525],{"class":139}," \"{name} {revision} {url}\"\n",[297,527,529],{"id":528},"adding-identifiers-to-spdx","Adding Identifiers to SPDX",[10,531,532,533,536],{},"SPDX 2.3 supports external references on packages. For each package that's missing identifiers, add ",[14,534,535],{},"ExternalRef"," entries:",[113,538,543],{"className":539,"code":541,"language":542},[540],"language-text","ExternalRef: SECURITY cpe23Type cpe:2.3:a:arm:mbed_tls:3.6.2:*:*:*:*:*:*:*\nExternalRef: PACKAGE-MANAGER purl pkg:github/Mbed-TLS/mbedtls@v3.6.2\n","text",[14,544,541],{"__ignoreMap":118},[10,546,547],{},"You can script this. A basic approach:",[113,549,553],{"className":550,"code":551,"language":552,"meta":118,"style":118},"language-python shiki shiki-themes github-light github-dark","#!/usr/bin/env python3\n\"\"\"Add CPE/PURL identifiers to west spdx output.\"\"\"\n\n# Mapping of Zephyr module paths to upstream identifiers\nCOMPONENT_MAP = {\n    \"mbedtls\": {\n        \"cpe\": \"cpe:2.3:a:arm:mbed_tls:{version}:*:*:*:*:*:*:*\",\n        \"purl\": \"pkg:github/Mbed-TLS/mbedtls@v{version}\",\n    },\n    \"mcuboot\": {\n        \"cpe\": \"cpe:2.3:a:mcuboot:mcuboot:{version}:*:*:*:*:*:*:*\",\n        \"purl\": \"pkg:github/mcu-tools/mcuboot@v{version}\",\n    },\n    \"cjson\": {\n        \"cpe\": \"cpe:2.3:a:cjson_project:cjson:{version}:*:*:*:*:*:*:*\",\n        \"purl\": \"pkg:github/DaveGamble/cJSON@v{version}\",\n    },\n    \"littlefs\": {\n        \"cpe\": \"cpe:2.3:a:littlefs_project:littlefs:{version}:*:*:*:*:*:*:*\",\n        \"purl\": \"pkg:github/littlefs-project/littlefs@v{version}\",\n    },\n}\n\ndef enrich_spdx(spdx_path, versions):\n    \"\"\"Insert ExternalRef lines after matching PackageName entries.\"\"\"\n    with open(spdx_path, \"r\") as f:\n        lines = f.readlines()\n\n    enriched = []\n    for line in lines:\n        enriched.append(line)\n        if line.startswith(\"PackageName:\"):\n            pkg_name = line.split(\":\", 1)[1].strip().lower()\n            for key, refs in COMPONENT_MAP.items():\n                if key in pkg_name and key in versions:\n                    ver = versions[key]\n                    enriched.append(\n                        f'ExternalRef: SECURITY cpe23Type {refs[\"cpe\"].format(version=ver)}\\n'\n                    )\n                    enriched.append(\n                        f'ExternalRef: PACKAGE-MANAGER purl {refs[\"purl\"].format(version=ver)}\\n'\n                    )\n\n    with open(spdx_path, \"w\") as f:\n        f.writelines(enriched)\n","python",[14,554,555,560,565,569,574,579,585,591,597,603,609,615,621,626,632,638,644,649,655,661,667,672,678,683,689,695,701,707,712,718,724,730,736,742,748,754,760,766,772,778,783,789,794,799,805],{"__ignoreMap":118},[122,556,557],{"class":124,"line":125},[122,558,559],{},"#!/usr/bin/env python3\n",[122,561,562],{"class":124,"line":132},[122,563,564],{},"\"\"\"Add CPE/PURL identifiers to west spdx output.\"\"\"\n",[122,566,567],{"class":124,"line":162},[122,568,166],{"emptyLinePlaceholder":165},[122,570,571],{"class":124,"line":169},[122,572,573],{},"# Mapping of Zephyr module paths to upstream identifiers\n",[122,575,576],{"class":124,"line":175},[122,577,578],{},"COMPONENT_MAP = {\n",[122,580,582],{"class":124,"line":581},6,[122,583,584],{},"    \"mbedtls\": {\n",[122,586,588],{"class":124,"line":587},7,[122,589,590],{},"        \"cpe\": \"cpe:2.3:a:arm:mbed_tls:{version}:*:*:*:*:*:*:*\",\n",[122,592,594],{"class":124,"line":593},8,[122,595,596],{},"        \"purl\": \"pkg:github/Mbed-TLS/mbedtls@v{version}\",\n",[122,598,600],{"class":124,"line":599},9,[122,601,602],{},"    },\n",[122,604,606],{"class":124,"line":605},10,[122,607,608],{},"    \"mcuboot\": {\n",[122,610,612],{"class":124,"line":611},11,[122,613,614],{},"        \"cpe\": \"cpe:2.3:a:mcuboot:mcuboot:{version}:*:*:*:*:*:*:*\",\n",[122,616,618],{"class":124,"line":617},12,[122,619,620],{},"        \"purl\": \"pkg:github/mcu-tools/mcuboot@v{version}\",\n",[122,622,624],{"class":124,"line":623},13,[122,625,602],{},[122,627,629],{"class":124,"line":628},14,[122,630,631],{},"    \"cjson\": {\n",[122,633,635],{"class":124,"line":634},15,[122,636,637],{},"        \"cpe\": \"cpe:2.3:a:cjson_project:cjson:{version}:*:*:*:*:*:*:*\",\n",[122,639,641],{"class":124,"line":640},16,[122,642,643],{},"        \"purl\": \"pkg:github/DaveGamble/cJSON@v{version}\",\n",[122,645,647],{"class":124,"line":646},17,[122,648,602],{},[122,650,652],{"class":124,"line":651},18,[122,653,654],{},"    \"littlefs\": {\n",[122,656,658],{"class":124,"line":657},19,[122,659,660],{},"        \"cpe\": \"cpe:2.3:a:littlefs_project:littlefs:{version}:*:*:*:*:*:*:*\",\n",[122,662,664],{"class":124,"line":663},20,[122,665,666],{},"        \"purl\": \"pkg:github/littlefs-project/littlefs@v{version}\",\n",[122,668,670],{"class":124,"line":669},21,[122,671,602],{},[122,673,675],{"class":124,"line":674},22,[122,676,677],{},"}\n",[122,679,681],{"class":124,"line":680},23,[122,682,166],{"emptyLinePlaceholder":165},[122,684,686],{"class":124,"line":685},24,[122,687,688],{},"def enrich_spdx(spdx_path, versions):\n",[122,690,692],{"class":124,"line":691},25,[122,693,694],{},"    \"\"\"Insert ExternalRef lines after matching PackageName entries.\"\"\"\n",[122,696,698],{"class":124,"line":697},26,[122,699,700],{},"    with open(spdx_path, \"r\") as f:\n",[122,702,704],{"class":124,"line":703},27,[122,705,706],{},"        lines = f.readlines()\n",[122,708,710],{"class":124,"line":709},28,[122,711,166],{"emptyLinePlaceholder":165},[122,713,715],{"class":124,"line":714},29,[122,716,717],{},"    enriched = []\n",[122,719,721],{"class":124,"line":720},30,[122,722,723],{},"    for line in lines:\n",[122,725,727],{"class":124,"line":726},31,[122,728,729],{},"        enriched.append(line)\n",[122,731,733],{"class":124,"line":732},32,[122,734,735],{},"        if line.startswith(\"PackageName:\"):\n",[122,737,739],{"class":124,"line":738},33,[122,740,741],{},"            pkg_name = line.split(\":\", 1)[1].strip().lower()\n",[122,743,745],{"class":124,"line":744},34,[122,746,747],{},"            for key, refs in COMPONENT_MAP.items():\n",[122,749,751],{"class":124,"line":750},35,[122,752,753],{},"                if key in pkg_name and key in versions:\n",[122,755,757],{"class":124,"line":756},36,[122,758,759],{},"                    ver = versions[key]\n",[122,761,763],{"class":124,"line":762},37,[122,764,765],{},"                    enriched.append(\n",[122,767,769],{"class":124,"line":768},38,[122,770,771],{},"                        f'ExternalRef: SECURITY cpe23Type {refs[\"cpe\"].format(version=ver)}\\n'\n",[122,773,775],{"class":124,"line":774},39,[122,776,777],{},"                    )\n",[122,779,781],{"class":124,"line":780},40,[122,782,765],{},[122,784,786],{"class":124,"line":785},41,[122,787,788],{},"                        f'ExternalRef: PACKAGE-MANAGER purl {refs[\"purl\"].format(version=ver)}\\n'\n",[122,790,792],{"class":124,"line":791},42,[122,793,777],{},[122,795,797],{"class":124,"line":796},43,[122,798,166],{"emptyLinePlaceholder":165},[122,800,802],{"class":124,"line":801},44,[122,803,804],{},"    with open(spdx_path, \"w\") as f:\n",[122,806,808],{"class":124,"line":807},45,[122,809,810],{},"        f.writelines(enriched)\n",[10,812,813,814,817],{},"This is a starting point — you'll need to extend the ",[14,815,816],{},"COMPONENT_MAP"," for every third-party module in your build.",[42,819,821],{"id":820},"documenting-vendor-hal-binary-blobs","Documenting Vendor HAL Binary Blobs",[10,823,824],{},"Vendor HAL modules require special treatment. You can't enumerate the contents of a pre-compiled binary blob, but CRA still requires you to document what's in your firmware.",[10,826,827,828,831],{},"For each binary blob, add a package entry with ",[14,829,830],{},"NOASSERTION"," for fields you genuinely cannot determine:",[113,833,836],{"className":834,"code":835,"language":542},[540],"PackageName: Nordic SoftDevice S140\nSPDXID: SPDXRef-softdevice-s140\nPackageVersion: 7.3.0\nPackageSupplier: Organization: Nordic Semiconductor\nPackageDownloadLocation: NOASSERTION\nFilesAnalyzed: false\nPackageVerificationCode: NOASSERTION (binary blob — source not available)\nPackageLicenseConcluded: LicenseRef-Nordic-Proprietary\nPackageLicenseDeclared: LicenseRef-Nordic-Proprietary\nPackageCopyrightText: Copyright Nordic Semiconductor ASA\nExternalRef: PACKAGE-MANAGER purl pkg:generic/nordic/softdevice-s140@7.3.0\nPackageComment: Pre-compiled binary blob provided by Nordic Semiconductor.\n  Source code not available. Vulnerability assessment depends on vendor advisories.\n",[14,837,835],{"__ignoreMap":118},[10,839,840],{},"Key points for binary blob documentation:",[62,842,843,850,856,863,869],{},[65,844,845,846,849],{},"Set ",[14,847,848],{},"FilesAnalyzed: false"," — you can't inspect the source",[65,851,852,853,855],{},"Use ",[14,854,830],{}," for verification codes and download locations you don't have",[65,857,858,859,862],{},"Add a ",[14,860,861],{},"PackageComment"," explaining why information is limited",[65,864,852,865,868],{},[14,866,867],{},"pkg:generic/"," PURL type for vendor-specific packages without a standard package manager",[65,870,871],{},"Track the vendor's security advisories separately — NVD won't cover these",[10,873,874],{},"Request SBOMs from your silicon vendors. More vendors are providing them (Nordic, NXP, and STMicroelectronics all have SBOM initiatives), but you may need to ask.",[42,876,878],{"id":877},"adding-invisible-components","Adding Invisible Components",[10,880,881,882,885],{},"Zephyr subsystems that are compiled in but don't appear as discrete SPDX packages need to be added manually. Which ones depend on your Kconfig — check your build's ",[14,883,884],{},".config"," file.",[10,887,888],{},"Common invisible components to add:",[206,890,891,904],{},[209,892,893],{},[212,894,895,898,901],{},[215,896,897],{},"Kconfig option",[215,899,900],{},"Component to add",[215,902,903],{},"How to determine version",[222,905,906,918,930,942,954,966,982,994,1006],{},[212,907,908,912,915],{},[227,909,910],{},[14,911,322],{},[227,913,914],{},"Zephyr Bluetooth Host Stack",[227,916,917],{},"Same as your Zephyr version",[212,919,920,925,928],{},[227,921,922],{},[14,923,924],{},"CONFIG_BT_CTLR=y",[227,926,927],{},"Zephyr Bluetooth Controller",[227,929,917],{},[212,931,932,937,940],{},[227,933,934],{},[14,935,936],{},"CONFIG_MQTT_LIB=y",[227,938,939],{},"Zephyr MQTT Library",[227,941,917],{},[212,943,944,949,952],{},[227,945,946],{},[14,947,948],{},"CONFIG_NET_TCP=y",[227,950,951],{},"Zephyr TCP/IP Stack",[227,953,917],{},[212,955,956,961,964],{},[227,957,958],{},[14,959,960],{},"CONFIG_HTTP_CLIENT=y",[227,962,963],{},"Zephyr HTTP Client",[227,965,917],{},[212,967,968,973,976],{},[227,969,970],{},[14,971,972],{},"CONFIG_CJSON_LIB=y",[227,974,975],{},"cJSON",[227,977,978,979,981],{},"Check ",[14,980,444],{}," revision",[212,983,984,989,992],{},[227,985,986],{},[14,987,988],{},"CONFIG_LWM2M=y",[227,990,991],{},"Zephyr LwM2M Engine",[227,993,917],{},[212,995,996,1001,1004],{},[227,997,998],{},[14,999,1000],{},"CONFIG_COAP=y",[227,1002,1003],{},"Zephyr CoAP Library",[227,1005,917],{},[212,1007,1008,1013,1016],{},[227,1009,1010],{},[14,1011,1012],{},"CONFIG_USB_DEVICE_STACK=y",[227,1014,1015],{},"Zephyr USB Device Stack",[227,1017,917],{},[10,1019,1020],{},"Script the detection from your build configuration:",[113,1022,1024],{"className":115,"code":1023,"language":117,"meta":118,"style":118},"# Extract enabled subsystems from the build config\ngrep -E \"^CONFIG_(BT|MQTT|NET_TCP|HTTP|CJSON|LWM2M|COAP|USB_DEVICE)=\" \\\n  build/zephyr/.config\n",[14,1025,1026,1031,1045],{"__ignoreMap":118},[122,1027,1028],{"class":124,"line":125},[122,1029,1030],{"class":128},"# Extract enabled subsystems from the build config\n",[122,1032,1033,1036,1039,1042],{"class":124,"line":132},[122,1034,1035],{"class":135},"grep",[122,1037,1038],{"class":143}," -E",[122,1040,1041],{"class":139}," \"^CONFIG_(BT|MQTT|NET_TCP|HTTP|CJSON|LWM2M|COAP|USB_DEVICE)=\"",[122,1043,1044],{"class":143}," \\\n",[122,1046,1047],{"class":124,"line":162},[122,1048,1049],{"class":139},"  build/zephyr/.config\n",[10,1051,1052],{},"For each enabled subsystem, add a package entry to your SPDX document with:",[62,1054,1055,1058,1069],{},[65,1056,1057],{},"The Zephyr version as the package version (these are part of the Zephyr source tree)",[65,1059,1060,1061,1064,1065,1068],{},"A relationship to the main Zephyr kernel package (",[14,1062,1063],{},"CONTAINS"," or ",[14,1066,1067],{},"DEPENDS_ON",")",[65,1070,1071],{},"A PURL pointing to the Zephyr project with the subsystem path",[113,1073,1076],{"className":1074,"code":1075,"language":542},[540],"PackageName: Zephyr Bluetooth Host Stack\nSPDXID: SPDXRef-zephyr-bluetooth\nPackageVersion: 3.7.0\nPackageSupplier: Organization: Zephyr Project\nPackageDownloadLocation: https://github.com/zephyrproject-rtos/zephyr\nFilesAnalyzed: false\nPackageLicenseConcluded: Apache-2.0\nPackageLicenseDeclared: Apache-2.0\nPackageCopyrightText: Copyright Zephyr Project Contributors\nExternalRef: PACKAGE-MANAGER purl pkg:github/zephyrproject-rtos/zephyr@v3.7.0#subsys/bluetooth\nRelationship: SPDXRef-zephyr-kernel CONTAINS SPDXRef-zephyr-bluetooth\n",[14,1077,1075],{"__ignoreMap":118},[42,1079,1081],{"id":1080},"merging-the-4-spdx-files-into-one-document","Merging the 4 SPDX Files into One Document",[10,1083,1084,1085,1087,1088,1090],{},"For submission to market surveillance authorities and for tooling compatibility, you'll want a single SPDX document rather than four separate files. The ",[14,1086,16],{}," ",[14,1089,261],{}," file provides relationships between the documents, but many tools expect a single file.",[10,1092,1093],{},"Use the SPDX tools Python library:",[113,1095,1097],{"className":115,"code":1096,"language":117,"meta":118,"style":118},"pip install spdx-tools\n\n# Validate individual files first\npyspdx-tv parse build/spdx/app.spdx\npyspdx-tv parse build/spdx/zephyr.spdx\n",[14,1098,1099,1110,1114,1119,1130],{"__ignoreMap":118},[122,1100,1101,1104,1107],{"class":124,"line":125},[122,1102,1103],{"class":135},"pip",[122,1105,1106],{"class":139}," install",[122,1108,1109],{"class":139}," spdx-tools\n",[122,1111,1112],{"class":124,"line":132},[122,1113,166],{"emptyLinePlaceholder":165},[122,1115,1116],{"class":124,"line":162},[122,1117,1118],{"class":128},"# Validate individual files first\n",[122,1120,1121,1124,1127],{"class":124,"line":169},[122,1122,1123],{"class":135},"pyspdx-tv",[122,1125,1126],{"class":139}," parse",[122,1128,1129],{"class":139}," build/spdx/app.spdx\n",[122,1131,1132,1134,1136],{"class":124,"line":175},[122,1133,1123],{"class":135},[122,1135,1126],{"class":139},[122,1137,1138],{"class":139}," build/spdx/zephyr.spdx\n",[10,1140,1141],{},"For merging, write a script that:",[1143,1144,1145,1148,1151,1154],"ol",{},[65,1146,1147],{},"Reads all four SPDX documents",[65,1149,1150],{},"Assigns unique SPDX IDs across documents (prefix with document origin to avoid collisions)",[65,1152,1153],{},"Combines all packages, files, and relationships into a single document",[65,1155,1156],{},"Updates the document namespace and creation info",[10,1158,1159,1160,1163],{},"Alternatively, use SPDX JSON format (",[14,1161,1162],{},"west spdx -d build --output-format json",") if your toolchain handles JSON better — the merge is simpler with JSON than with SPDX tag-value format.",[42,1165,1167],{"id":1166},"converting-to-cyclonedx","Converting to CycloneDX",[10,1169,1170],{},"If your supply chain requires CycloneDX, you can convert the enriched SPDX output. Be aware that the conversion is lossy.",[113,1172,1174],{"className":115,"code":1173,"language":117,"meta":118,"style":118},"# Install the CycloneDX CLI tool\nnpm install -g @cyclonedx/cyclonedx-cli\n\n# Convert SPDX to CycloneDX\ncyclonedx-cli convert \\\n  --input-file build/spdx/merged.spdx \\\n  --input-format spdxjson \\\n  --output-file firmware-sbom.cdx.json \\\n  --output-format json\n",[14,1175,1176,1181,1194,1198,1203,1213,1223,1233,1243],{"__ignoreMap":118},[122,1177,1178],{"class":124,"line":125},[122,1179,1180],{"class":128},"# Install the CycloneDX CLI tool\n",[122,1182,1183,1186,1188,1191],{"class":124,"line":132},[122,1184,1185],{"class":135},"npm",[122,1187,1106],{"class":139},[122,1189,1190],{"class":143}," -g",[122,1192,1193],{"class":139}," @cyclonedx/cyclonedx-cli\n",[122,1195,1196],{"class":124,"line":162},[122,1197,166],{"emptyLinePlaceholder":165},[122,1199,1200],{"class":124,"line":169},[122,1201,1202],{"class":128},"# Convert SPDX to CycloneDX\n",[122,1204,1205,1208,1211],{"class":124,"line":175},[122,1206,1207],{"class":135},"cyclonedx-cli",[122,1209,1210],{"class":139}," convert",[122,1212,1044],{"class":143},[122,1214,1215,1218,1221],{"class":124,"line":581},[122,1216,1217],{"class":143},"  --input-file",[122,1219,1220],{"class":139}," build/spdx/merged.spdx",[122,1222,1044],{"class":143},[122,1224,1225,1228,1231],{"class":124,"line":587},[122,1226,1227],{"class":143},"  --input-format",[122,1229,1230],{"class":139}," spdxjson",[122,1232,1044],{"class":143},[122,1234,1235,1238,1241],{"class":124,"line":593},[122,1236,1237],{"class":143},"  --output-file",[122,1239,1240],{"class":139}," firmware-sbom.cdx.json",[122,1242,1044],{"class":143},[122,1244,1245,1248],{"class":124,"line":599},[122,1246,1247],{"class":143},"  --output-format",[122,1249,1250],{"class":139}," json\n",[10,1252,1253],{},[58,1254,1255],{},"What gets lost in conversion:",[62,1257,1258,1261,1264],{},[65,1259,1260],{},"SPDX file-level entries (CycloneDX focuses on components, not individual source files)",[65,1262,1263],{},"Some relationship types that don't have CycloneDX equivalents",[65,1265,1266],{},"SPDX-specific annotations and review information",[10,1268,1269],{},[58,1270,1271],{},"What converts cleanly:",[62,1273,1274,1277,1280,1283],{},[65,1275,1276],{},"Package names, versions, and suppliers",[65,1278,1279],{},"CPE and PURL identifiers (this is why enrichment matters — identifiers survive conversion)",[65,1281,1282],{},"License information",[65,1284,1285],{},"Dependency relationships (mapped to CycloneDX dependency graph)",[10,1287,1288],{},"If you need both formats, generate the enriched SPDX as your source of truth and derive CycloneDX from it. Don't maintain two parallel SBOMs.",[42,1290,1292],{"id":1291},"running-vulnerability-scanners-against-the-enriched-sbom","Running Vulnerability Scanners Against the Enriched SBOM",[10,1294,1295],{},"With CPE/PURL identifiers in place, vulnerability scanners can actually do their job.",[297,1297,1299],{"id":1298},"osv-scanner","OSV-Scanner",[113,1301,1303],{"className":115,"code":1302,"language":117,"meta":118,"style":118},"# OSV-Scanner works well with SPDX\nosv-scanner --sbom build/spdx/merged-enriched.spdx\n",[14,1304,1305,1310],{"__ignoreMap":118},[122,1306,1307],{"class":124,"line":125},[122,1308,1309],{"class":128},"# OSV-Scanner works well with SPDX\n",[122,1311,1312,1314,1317],{"class":124,"line":132},[122,1313,1298],{"class":135},[122,1315,1316],{"class":143}," --sbom",[122,1318,1319],{"class":139}," build/spdx/merged-enriched.spdx\n",[10,1321,1322],{},"OSV-Scanner queries the OSV database, which aggregates vulnerabilities from multiple sources including NVD, GitHub Advisories, and project-specific databases.",[297,1324,1325],{"id":285},"Grype",[113,1327,1329],{"className":115,"code":1328,"language":117,"meta":118,"style":118},"# Grype supports both SPDX and CycloneDX\ngrype sbom:build/spdx/merged-enriched.spdx\n\n# Or against the CycloneDX version\ngrype sbom:firmware-sbom.cdx.json\n",[14,1330,1331,1336,1343,1347,1352],{"__ignoreMap":118},[122,1332,1333],{"class":124,"line":125},[122,1334,1335],{"class":128},"# Grype supports both SPDX and CycloneDX\n",[122,1337,1338,1340],{"class":124,"line":132},[122,1339,285],{"class":135},[122,1341,1342],{"class":139}," sbom:build/spdx/merged-enriched.spdx\n",[122,1344,1345],{"class":124,"line":162},[122,1346,166],{"emptyLinePlaceholder":165},[122,1348,1349],{"class":124,"line":169},[122,1350,1351],{"class":128},"# Or against the CycloneDX version\n",[122,1353,1354,1356],{"class":124,"line":175},[122,1355,285],{"class":135},[122,1357,1358],{"class":139}," sbom:firmware-sbom.cdx.json\n",[297,1360,1362],{"id":1361},"interpreting-results","Interpreting Results",[10,1364,1365],{},"Expect false positives. Embedded firmware often uses stripped-down configurations of libraries. A vulnerability in Mbed TLS's X.509 parsing doesn't affect your build if you've disabled X.509 entirely via Kconfig.",[10,1367,1368],{},"This is where VEX (Vulnerability Exploitability eXchange) documents come in. For each scanner result, determine:",[62,1370,1371,1377,1383],{},[65,1372,1373,1376],{},[58,1374,1375],{},"Affected:"," The vulnerable code path exists in your build and is reachable",[65,1378,1379,1382],{},[58,1380,1381],{},"Not affected:"," The vulnerable code is compiled out (Kconfig), not reachable, or mitigated by other controls",[65,1384,1385,1388],{},[58,1386,1387],{},"Under investigation:"," You need to analyse further",[10,1390,1391,1392,1396],{},"Document your VEX assessments — CRA Article 14 requires you to track and communicate vulnerability status. See our ",[31,1393,1395],{"href":1394},"/blog/cra-article-14-vulnerability-reporting/","vulnerability reporting guide"," for the full process.",[42,1398,1400],{"id":1399},"cicd-integration-sbom-on-every-build","CI/CD Integration: SBOM on Every Build",[10,1402,1403],{},"Generate the SBOM as part of your CI pipeline, not as a manual step. This ensures every release has a corresponding SBOM and prevents drift between what's documented and what's shipped.",[113,1405,1409],{"className":1406,"code":1407,"language":1408,"meta":118,"style":118},"language-yaml shiki shiki-themes github-light github-dark","# Example GitHub Actions workflow\nname: Firmware Build + SBOM\n\non:\n  push:\n    tags: [\"v*\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    container:\n      image: ghcr.io/zephyrproject-rtos/ci:v0.27\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: West init and update\n        run: |\n          west init -l .\n          west update\n\n      - name: Build firmware\n        run: west build -p always -b ${{ env.BOARD }}\n\n      - name: Generate SPDX\n        run: west spdx -d build -n \"https://your-company.com/spdx/${{ github.ref_name }}\"\n\n      - name: Enrich SBOM\n        run: python scripts/enrich-sbom.py build/spdx/ --manifest west.yml\n\n      - name: Scan for vulnerabilities\n        run: |\n          osv-scanner --sbom build/spdx/merged-enriched.spdx\n\n      - name: Upload SBOM artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: sbom-${{ github.ref_name }}\n          path: |\n            build/spdx/merged-enriched.spdx\n            firmware-sbom.cdx.json\n","yaml",[14,1410,1411,1416,1429,1433,1441,1448,1462,1466,1473,1480,1490,1497,1507,1511,1518,1531,1535,1546,1557,1562,1567,1571,1582,1591,1595,1606,1615,1619,1630,1639,1643,1654,1662,1667,1671,1682,1692,1699,1709,1718,1723],{"__ignoreMap":118},[122,1412,1413],{"class":124,"line":125},[122,1414,1415],{"class":128},"# Example GitHub Actions workflow\n",[122,1417,1418,1422,1426],{"class":124,"line":132},[122,1419,1421],{"class":1420},"s9eBZ","name",[122,1423,1425],{"class":1424},"sVt8B",": ",[122,1427,1428],{"class":139},"Firmware Build + SBOM\n",[122,1430,1431],{"class":124,"line":162},[122,1432,166],{"emptyLinePlaceholder":165},[122,1434,1435,1438],{"class":124,"line":169},[122,1436,1437],{"class":143},"on",[122,1439,1440],{"class":1424},":\n",[122,1442,1443,1446],{"class":124,"line":175},[122,1444,1445],{"class":1420},"  push",[122,1447,1440],{"class":1424},[122,1449,1450,1453,1456,1459],{"class":124,"line":581},[122,1451,1452],{"class":1420},"    tags",[122,1454,1455],{"class":1424},": [",[122,1457,1458],{"class":139},"\"v*\"",[122,1460,1461],{"class":1424},"]\n",[122,1463,1464],{"class":124,"line":587},[122,1465,166],{"emptyLinePlaceholder":165},[122,1467,1468,1471],{"class":124,"line":593},[122,1469,1470],{"class":1420},"jobs",[122,1472,1440],{"class":1424},[122,1474,1475,1478],{"class":124,"line":599},[122,1476,1477],{"class":1420},"  build",[122,1479,1440],{"class":1424},[122,1481,1482,1485,1487],{"class":124,"line":605},[122,1483,1484],{"class":1420},"    runs-on",[122,1486,1425],{"class":1424},[122,1488,1489],{"class":139},"ubuntu-latest\n",[122,1491,1492,1495],{"class":124,"line":611},[122,1493,1494],{"class":1420},"    container",[122,1496,1440],{"class":1424},[122,1498,1499,1502,1504],{"class":124,"line":617},[122,1500,1501],{"class":1420},"      image",[122,1503,1425],{"class":1424},[122,1505,1506],{"class":139},"ghcr.io/zephyrproject-rtos/ci:v0.27\n",[122,1508,1509],{"class":124,"line":623},[122,1510,166],{"emptyLinePlaceholder":165},[122,1512,1513,1516],{"class":124,"line":628},[122,1514,1515],{"class":1420},"    steps",[122,1517,1440],{"class":1424},[122,1519,1520,1523,1526,1528],{"class":124,"line":634},[122,1521,1522],{"class":1424},"      - ",[122,1524,1525],{"class":1420},"uses",[122,1527,1425],{"class":1424},[122,1529,1530],{"class":139},"actions/checkout@v4\n",[122,1532,1533],{"class":124,"line":640},[122,1534,166],{"emptyLinePlaceholder":165},[122,1536,1537,1539,1541,1543],{"class":124,"line":646},[122,1538,1522],{"class":1424},[122,1540,1421],{"class":1420},[122,1542,1425],{"class":1424},[122,1544,1545],{"class":139},"West init and update\n",[122,1547,1548,1551,1553],{"class":124,"line":651},[122,1549,1550],{"class":1420},"        run",[122,1552,1425],{"class":1424},[122,1554,1556],{"class":1555},"szBVR","|\n",[122,1558,1559],{"class":124,"line":657},[122,1560,1561],{"class":139},"          west init -l .\n",[122,1563,1564],{"class":124,"line":663},[122,1565,1566],{"class":139},"          west update\n",[122,1568,1569],{"class":124,"line":669},[122,1570,166],{"emptyLinePlaceholder":165},[122,1572,1573,1575,1577,1579],{"class":124,"line":674},[122,1574,1522],{"class":1424},[122,1576,1421],{"class":1420},[122,1578,1425],{"class":1424},[122,1580,1581],{"class":139},"Build firmware\n",[122,1583,1584,1586,1588],{"class":124,"line":680},[122,1585,1550],{"class":1420},[122,1587,1425],{"class":1424},[122,1589,1590],{"class":139},"west build -p always -b ${{ env.BOARD }}\n",[122,1592,1593],{"class":124,"line":685},[122,1594,166],{"emptyLinePlaceholder":165},[122,1596,1597,1599,1601,1603],{"class":124,"line":691},[122,1598,1522],{"class":1424},[122,1600,1421],{"class":1420},[122,1602,1425],{"class":1424},[122,1604,1605],{"class":139},"Generate SPDX\n",[122,1607,1608,1610,1612],{"class":124,"line":697},[122,1609,1550],{"class":1420},[122,1611,1425],{"class":1424},[122,1613,1614],{"class":139},"west spdx -d build -n \"https://your-company.com/spdx/${{ github.ref_name }}\"\n",[122,1616,1617],{"class":124,"line":703},[122,1618,166],{"emptyLinePlaceholder":165},[122,1620,1621,1623,1625,1627],{"class":124,"line":709},[122,1622,1522],{"class":1424},[122,1624,1421],{"class":1420},[122,1626,1425],{"class":1424},[122,1628,1629],{"class":139},"Enrich SBOM\n",[122,1631,1632,1634,1636],{"class":124,"line":714},[122,1633,1550],{"class":1420},[122,1635,1425],{"class":1424},[122,1637,1638],{"class":139},"python scripts/enrich-sbom.py build/spdx/ --manifest west.yml\n",[122,1640,1641],{"class":124,"line":720},[122,1642,166],{"emptyLinePlaceholder":165},[122,1644,1645,1647,1649,1651],{"class":124,"line":726},[122,1646,1522],{"class":1424},[122,1648,1421],{"class":1420},[122,1650,1425],{"class":1424},[122,1652,1653],{"class":139},"Scan for vulnerabilities\n",[122,1655,1656,1658,1660],{"class":124,"line":732},[122,1657,1550],{"class":1420},[122,1659,1425],{"class":1424},[122,1661,1556],{"class":1555},[122,1663,1664],{"class":124,"line":738},[122,1665,1666],{"class":139},"          osv-scanner --sbom build/spdx/merged-enriched.spdx\n",[122,1668,1669],{"class":124,"line":744},[122,1670,166],{"emptyLinePlaceholder":165},[122,1672,1673,1675,1677,1679],{"class":124,"line":750},[122,1674,1522],{"class":1424},[122,1676,1421],{"class":1420},[122,1678,1425],{"class":1424},[122,1680,1681],{"class":139},"Upload SBOM artifacts\n",[122,1683,1684,1687,1689],{"class":124,"line":756},[122,1685,1686],{"class":1420},"        uses",[122,1688,1425],{"class":1424},[122,1690,1691],{"class":139},"actions/upload-artifact@v4\n",[122,1693,1694,1697],{"class":124,"line":762},[122,1695,1696],{"class":1420},"        with",[122,1698,1440],{"class":1424},[122,1700,1701,1704,1706],{"class":124,"line":768},[122,1702,1703],{"class":1420},"          name",[122,1705,1425],{"class":1424},[122,1707,1708],{"class":139},"sbom-${{ github.ref_name }}\n",[122,1710,1711,1714,1716],{"class":124,"line":774},[122,1712,1713],{"class":1420},"          path",[122,1715,1425],{"class":1424},[122,1717,1556],{"class":1555},[122,1719,1720],{"class":124,"line":780},[122,1721,1722],{"class":139},"            build/spdx/merged-enriched.spdx\n",[122,1724,1725],{"class":124,"line":785},[122,1726,1727],{"class":139},"            firmware-sbom.cdx.json\n",[10,1729,1730],{},"Tag the SBOM with the release version. When a market surveillance authority requests documentation, you need to produce the SBOM that matches the specific firmware version deployed.",[42,1732,1734,1735,1738],{"id":1733},"nordic-west-ncs-sbom-comparison","Nordic ",[14,1736,1737],{},"west ncs-sbom"," Comparison",[10,1740,1741,1742,1744],{},"If you're using the nRF Connect SDK (which is built on Zephyr), Nordic provides ",[14,1743,1737],{}," — a separate SBOM tool with different goals.",[206,1746,1747,1764],{},[209,1748,1749],{},[212,1750,1751,1754,1759],{},[215,1752,1753],{},"Feature",[215,1755,1756,1758],{},[14,1757,16],{}," (Zephyr)",[215,1760,1761,1763],{},[14,1762,1737],{}," (Nordic)",[222,1765,1766,1777,1788,1798,1809,1820],{},[212,1767,1768,1771,1774],{},[227,1769,1770],{},"Primary focus",[227,1772,1773],{},"Build-accurate source enumeration",[227,1775,1776],{},"License compliance",[212,1778,1779,1782,1785],{},[227,1780,1781],{},"Output formats",[227,1783,1784],{},"SPDX 2.3 tag-value/JSON",[227,1786,1787],{},"SPDX, custom report",[212,1789,1790,1793,1796],{},[227,1791,1792],{},"CPE/PURL identifiers",[227,1794,1795],{},"No",[227,1797,1795],{},[212,1799,1800,1803,1806],{},[227,1801,1802],{},"Binary blob handling",[227,1804,1805],{},"Listed but opaque",[227,1807,1808],{},"Better Nordic-specific coverage",[212,1810,1811,1814,1817],{},[227,1812,1813],{},"Subsystem enumeration",[227,1815,1816],{},"Rolled into kernel",[227,1818,1819],{},"Similar limitation",[212,1821,1822,1825,1828],{},[227,1823,1824],{},"CRA vulnerability scanning",[227,1826,1827],{},"Not useful without enrichment",[227,1829,1827],{},[10,1831,1832,1833,1835],{},"Neither tool solves the CRA SBOM problem on its own. Both require the enrichment workflow described in this tutorial. The ",[14,1834,1737],{}," tool is better for license compliance documentation but has the same gaps for security-focused SBOM use.",[42,1837,1839,1840,1842],{"id":1838},"checklist-from-west-spdx-to-cra-compliant-sbom","Checklist: From ",[14,1841,16],{}," to CRA-Compliant SBOM",[10,1844,1845],{},[58,1846,1847],{},"Generation",[62,1849,1852,1863,1869],{"className":1850},[1851],"contains-task-list",[65,1853,1856,1087,1860,1862],{"className":1854},[1855],"task-list-item",[1857,1858],"input",{"disabled":165,"type":1859},"checkbox",[14,1861,16],{}," runs on every release build",[65,1864,1866,1868],{"className":1865},[1855],[1857,1867],{"disabled":165,"type":1859}," SPDX namespace uses your company's domain",[65,1870,1872,1874],{"className":1871},[1855],[1857,1873],{"disabled":165,"type":1859}," MCUboot SBOM generated separately and merged",[10,1876,1877],{},[58,1878,1879],{},"Enrichment",[62,1881,1883,1889,1895,1901],{"className":1882},[1851],[65,1884,1886,1888],{"className":1885},[1855],[1857,1887],{"disabled":165,"type":1859}," Every package has a CPE identifier (or documented reason for omission)",[65,1890,1892,1894],{"className":1891},[1855],[1857,1893],{"disabled":165,"type":1859}," Every package has a PURL identifier",[65,1896,1898,1900],{"className":1897},[1855],[1857,1899],{"disabled":165,"type":1859}," Package versions match upstream release versions (not just commit hashes)",[65,1902,1904,1906],{"className":1903},[1855],[1857,1905],{"disabled":165,"type":1859}," Supplier information populated for all packages",[10,1908,1909],{},[58,1910,1911],{},"Invisible components",[62,1913,1915,1923,1929,1937,1945],{"className":1914},[1851],[65,1916,1918,1920,1921,1068],{"className":1917},[1855],[1857,1919],{"disabled":165,"type":1859}," Bluetooth stack listed as separate component (if ",[14,1922,322],{},[65,1924,1926,1928],{"className":1925},[1855],[1857,1927],{"disabled":165,"type":1859}," MQTT/CoAP/HTTP libraries listed (if enabled)",[65,1930,1932,1934,1935,1068],{"className":1931},[1855],[1857,1933],{"disabled":165,"type":1859}," cJSON listed separately (if ",[14,1936,972],{},[65,1938,1940,1942,1943,1068],{"className":1939},[1855],[1857,1941],{"disabled":165,"type":1859}," USB stack listed (if ",[14,1944,1012],{},[65,1946,1948,1950],{"className":1947},[1855],[1857,1949],{"disabled":165,"type":1859}," All Kconfig-enabled subsystems audited",[10,1952,1953],{},[58,1954,1955],{},"Binary blobs",[62,1957,1959,1965,1971,1979],{"className":1958},[1851],[65,1960,1962,1964],{"className":1961},[1855],[1857,1963],{"disabled":165,"type":1859}," Vendor HAL modules documented with available version info",[65,1966,1968,1970],{"className":1967},[1855],[1857,1969],{"disabled":165,"type":1859}," Pre-compiled radio stacks (SoftDevice, WiFi firmware) listed",[65,1972,1974,1087,1976,1978],{"className":1973},[1855],[1857,1975],{"disabled":165,"type":1859},[14,1977,848],{}," set for opaque binaries",[65,1980,1982,1984],{"className":1981},[1855],[1857,1983],{"disabled":165,"type":1859}," Vendor SBOM requested from silicon supplier",[10,1986,1987],{},[58,1988,1989],{},"Vulnerability scanning",[62,1991,1993,2002,2008,2014],{"className":1992},[1851],[65,1994,1996,1998,1999,2001],{"className":1995},[1855],[1857,1997],{"disabled":165,"type":1859}," Scanner runs against enriched SBOM (not raw ",[14,2000,16],{}," output)",[65,2003,2005,2007],{"className":2004},[1855],[1857,2006],{"disabled":165,"type":1859}," False positives triaged via VEX assessment",[65,2009,2011,2013],{"className":2010},[1855],[1857,2012],{"disabled":165,"type":1859}," MCUboot vulnerabilities tracked via project advisories (not just NVD)",[65,2015,2017,2019],{"className":2016},[1855],[1857,2018],{"disabled":165,"type":1859}," Scan results archived per release",[10,2021,2022],{},[58,2023,2024],{},"Format and delivery",[62,2026,2028,2034,2040,2046],{"className":2027},[1851],[65,2029,2031,2033],{"className":2030},[1855],[1857,2032],{"disabled":165,"type":1859}," Four SPDX files merged into single document",[65,2035,2037,2039],{"className":2036},[1855],[1857,2038],{"disabled":165,"type":1859}," CycloneDX version generated if required by supply chain",[65,2041,2043,2045],{"className":2042},[1855],[1857,2044],{"disabled":165,"type":1859}," SBOM included in Annex VII technical documentation",[65,2047,2049,2051],{"className":2048},[1855],[1857,2050],{"disabled":165,"type":1859}," SBOM versioned and archived alongside firmware releases",[10,2053,2054,2055,2058,2059,2063],{},"For a broader view of Zephyr CRA compliance beyond SBOM, see our ",[31,2056,2057],{"href":38},"complete Zephyr CRA guide",". For the ",[31,2060,2062],{"href":2061},"/blog/cra-annex-i-essential-requirements-checklist/","Annex I essential requirements checklist",", see the full mapping of every requirement to implementation actions.",[2065,2066],"hr",{},[10,2068,2069],{},[2070,2071,2072],"em",{},"Based on Zephyr Project documentation (v3.7+), SPDX 2.3 specification, CycloneDX 1.7 specification, NTIA minimum SBOM elements (2021), and Regulation EU 2024/2847 Annex I Part II. This does not constitute legal advice.",[42,2074,2076],{"id":2075},"sources","Sources",[62,2078,2079,2087,2094,2101,2108,2115,2122,2129,2136],{},[65,2080,2081],{},[31,2082,2086],{"href":2083,"rel":2084},"https://eur-lex.europa.eu/eli/reg/2024/2847/oj/eng",[2085],"nofollow","Regulation (EU) 2024/2847 — Cyber Resilience Act (full text)",[65,2088,2089],{},[31,2090,2093],{"href":2091,"rel":2092},"https://docs.zephyrproject.org/latest/develop/west/zephyr-cmds.html",[2085],"Zephyr Project — west spdx command",[65,2095,2096],{},[31,2097,2100],{"href":2098,"rel":2099},"https://zephyrproject.org/practical-sbom-management-with-zephyr-and-spdx-benjamin-cabe-the-linux-foundation/",[2085],"Zephyr Project — Practical SBOM Management with Zephyr and SPDX",[65,2102,2103],{},[31,2104,2107],{"href":2105,"rel":2106},"https://spdx.github.io/spdx-spec/v2.3/",[2085],"SPDX 2.3 Specification",[65,2109,2110],{},[31,2111,2114],{"href":2112,"rel":2113},"https://cyclonedx.org/specification/overview/",[2085],"OWASP CycloneDX Specification",[65,2116,2117],{},[31,2118,2121],{"href":2119,"rel":2120},"https://www.ntia.gov/sites/default/files/publications/sbom_minimum_elements_report_0.pdf",[2085],"NTIA — Minimum Elements for a Software Bill of Materials (2021)",[65,2123,2124],{},[31,2125,2128],{"href":2126,"rel":2127},"https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/scripts/west_commands/ncs-sbom.html",[2085],"Nordic Semiconductor — west ncs-sbom",[65,2130,2131],{},[31,2132,2135],{"href":2133,"rel":2134},"https://google.github.io/osv-scanner/",[2085],"OSV-Scanner — Vulnerability scanning",[65,2137,2138],{},[31,2139,2142],{"href":2140,"rel":2141},"https://github.com/anchore/grype",[2085],"Grype — Vulnerability scanner for container images and filesystems",[2144,2145,2146],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":118,"searchDepth":132,"depth":132,"links":2148},[2149,2151,2152,2159,2163,2164,2165,2166,2167,2172,2173,2175,2177],{"id":44,"depth":132,"text":2150},"What west spdx Gives You (and What It Doesn't)",{"id":104,"depth":132,"text":105},{"id":294,"depth":132,"text":295,"children":2153},[2154,2155,2156,2157,2158],{"id":299,"depth":162,"text":300},{"id":315,"depth":162,"text":316},{"id":332,"depth":162,"text":333},{"id":358,"depth":162,"text":359},{"id":368,"depth":162,"text":369},{"id":386,"depth":132,"text":387,"children":2160},[2161,2162],{"id":393,"depth":162,"text":394},{"id":528,"depth":162,"text":529},{"id":820,"depth":132,"text":821},{"id":877,"depth":132,"text":878},{"id":1080,"depth":132,"text":1081},{"id":1166,"depth":132,"text":1167},{"id":1291,"depth":132,"text":1292,"children":2168},[2169,2170,2171],{"id":1298,"depth":162,"text":1299},{"id":285,"depth":162,"text":1325},{"id":1361,"depth":162,"text":1362},{"id":1399,"depth":132,"text":1400},{"id":1733,"depth":132,"text":2174},"Nordic west ncs-sbom Comparison",{"id":1838,"depth":132,"text":2176},"Checklist: From west spdx to CRA-Compliant SBOM",{"id":2075,"depth":132,"text":2076},"2026-01-08","Zephyr's west spdx misses CPE/PURL identifiers and binary blobs needed for CRA vulnerability scanning. Full SBOM enrichment tutorial.","md","/images/blog/previews/zephyr-sbom.svg",[2183,16,2184,2185,2186,2187],"Zephyr SBOM","Zephyr CRA SBOM","SBOM embedded firmware","Zephyr CycloneDX","Zephyr vulnerability scanning",{},"/blog/zephyr-sbom-cra-compliance","15 min",{"title":5,"description":2179},"blog/zephyr-sbom-cra-compliance","kjA4jDzZ2TH3obxqvobY29MXLroJuArlYh_koJ4rqz0",1775939691849]