An MBOM, or manufacturing bill of materials, means two different things. In ERP and PLM systems it is the parts list and routing a factory uses to build a physical product. In CycloneDX it is a BOM whose formulation section records how a component was made: the formulas, workflows, tasks, steps, tools and triggers behind one build.
This post covers the CycloneDX meaning: every object in the formulation model, a schema valid MBOM for a firmware build line, how it compares with SLSA provenance and the SPDX 3 Build profile, and how to produce, validate, link and sign one with open source tools.
Key takeaways
- "MBOM" is overloaded: an ERP or PLM parts and routing structure, or a CycloneDX BOM whose
formulationproperty describes how something was made. - CycloneDX added
formulationin 1.5 (tagged 26 June 2023). In 1.7 its scope widens to any referencable object in the BOM, including the BOM itself. - The model is a hierarchy: formulation, formula, workflows, tasks, steps, commands, with triggers, workspaces, inputs and outputs attached.
- OWASP CycloneDX published its Authoritative Guide to MBOM on 21 October 2025, the day CycloneDX 1.7 was tagged. It recommends a separate MBOM linked from the SBOM with a
formulationexternal reference. - SLSA provenance is a compact, platform signed claim about one build; the SPDX 3 Build profile is a graph node with inputs and outputs; the MBOM is the most detailed of the three.
- Tooling is thin: cdxgen's
--include-formulationgives you a seed, the CycloneDX CLI validates, and the rest is CI scripting. - Safeguard does not generate MBOMs; its fit is around them.
The two meanings of MBOM
The ERP and PLM manufacturing BOM
In discrete manufacturing, a product usually has more than one bill of materials. Engineering keeps an EBOM, the design view: what the product is made of, as designed. Manufacturing keeps an MBOM, the build view: which purchased parts, subassemblies, consumables and packaging go into the product, in which quantities, at which operation and on which line. The MBOM is often restructured from the EBOM so that it follows the order in which a factory actually assembles things, and it is tied to routings (the sequence of operations and work centers) in an ERP or manufacturing execution system.
That MBOM answers questions such as "which supplier lot of capacitors went into serial numbers 10,400 to 10,900." It is an operational record for planning, costing and traceability, described by industry practice rather than one standard, and rarely shared outside the company.
The CycloneDX MBOM
CycloneDX uses "MBOM" for something narrower and more security focused. The CycloneDX MBOM capability page describes it as "declared and observed formulation for reproducibility throughout the product lifecycle." In the specification, the work is done by a single top level property, formulation, which holds an array of formula objects. Each formula lists the components and services used in a process and the workflows that ran.
The page frames the purpose as traceability "from design to deployment," reproducibility and spotting deviations, with automotive, aerospace and healthcare named as regulated industries where it matters. Although the vocabulary is software flavored, the Authoritative Guide to MBOM says formulation can describe how "anything came to be": software builds, hardware assembly, service deployment, cryptographic validation, data set curation, compliance artifacts and the BOM document itself.
Side by side
| Question | ERP or PLM MBOM | CycloneDX MBOM |
|---|---|---|
| What it lists | Parts, subassemblies, quantities, units of measure | Components and services used in a process, plus the process itself |
| What it describes | What goes into a physical product, per operation | How a component, service or BOM was built, tested, deployed or certified |
| Structure | Hierarchical parts tree tied to routings and work centers | Formula, workflows, tasks, steps, commands, with triggers, inputs and outputs |
| Format | Vendor specific tables inside ERP, PLM or MES | Open standard, JSON (and XML and Protocol Buffers in CycloneDX generally) |
| Typical audience | Planners, procurement, production engineering | Security reviewers, auditors, regulators, downstream buyers, incident responders |
| Security role | Indirect (lot traceability, counterfeit investigations) | Direct (build integrity, reproducibility, evidence that controls ran) |
| Shared outside the company | Rarely | Selectively, under access control |
The two can meet: a formula can point to an ERP routing through an external reference. But neither replaces the other.
If you came here looking for the hardware side (what chips and boards a device contains), that is an HBOM, which the series covers in the HBOM complete guide and how to create an HBOM in CycloneDX.
A dated timeline of formulation in CycloneDX
The dates below come from the CycloneDX specification release tags on GitHub, the published JSON schemas and the Ecma International ECMA-424 page.
| Date | Event | What changed for MBOM |
|---|---|---|
| Before 1.5 | CycloneDX 1.4 schema | No formulation property, no workflow objects, no metadata.lifecycles and no formulation external reference type |
| 26 June 2023 | CycloneDX 1.5 tagged | formulation added, with formula, workflow, task, step, command, trigger, event, workspace, volume, inputType and outputType; metadata.lifecycles added; external reference type formulation added |
| 9 April 2024 | CycloneDX 1.6 tagged | Formulation model unchanged in substance; metadata.manufacture deprecated in favor of component.manufacturer |
| June 2024 | ECMA-424 first edition | CycloneDX 1.6 published as an Ecma standard |
| 21 October 2025 | CycloneDX 1.7 tagged; Authoritative Guide to MBOM, first edition | formulation description widened to any referencable object, including metadata, declarations and the BOM itself; metadata.distributionConstraints with a TLP classification added |
| Current | ECMA-424 page | The current edition defines CycloneDX 1.7 and lists formulation processes among the things the standard specifies |
The formulation field names have been stable from 1.5 through 1.7, and the 1.7 Traffic Light Protocol field suits MBOMs, which often hold detail you do not want forwarded.
For the wider version history, see the CycloneDX specification deep dive.
How CycloneDX expresses "how it was made"
The Authoritative Guide to MBOM summarizes the structure as a chain: formulation, formula, workflows, tasks, steps. The schema fills in the rest. Every field name below is taken from the CycloneDX 1.7 JSON schema.
formulation and formula
formulation is a top level array on the BOM, next to components, services, dependencies and vulnerabilities. Each entry is a formula.
A formula has an optional bom-ref, plus components, services, workflows and properties. Its components and services are what the process used: source trees, compilers, build containers, test rigs, signing services. The guide notes that any service a formula references must be declared in the same BOM, and its example lists build tools under the formula's own components rather than the top level array, so the MBOM can live in its own file.
workflows
A workflow is a logical phase of the process, organized as a directed acyclic graph of tasks; in CI terms, a pipeline run. Required fields are bom-ref, uid and taskTypes. The guide calls it a "near subclass" of a task: it has every task field plus tasks and taskDependencies.
taskDependencies reuses the top level dependency object (ref plus dependsOn) to record observed execution order, and the guide says the graph should be acyclic. runtimeTopology uses the same type for the runtime pieces of the run. Because a workflow is shaped like a task, one workflow can appear as a task in another, which is how you model a release pipeline that calls a reusable build pipeline.
tasks and task types
A task is one unit of work. Required fields are bom-ref, uid and taskTypes; optional fields are name, description, resourceReferences, trigger, steps, inputs, outputs, timeStart, timeEnd, workspaces, runtimeTopology and properties. taskTypes draws from a fixed enumeration:
| taskType | Meaning in the guide |
|---|---|
copy | Copies software or data used by other tasks |
clone | Clones a repository to retrieve source or data |
lint | Checks source for programmatic and stylistic errors |
scan | Scans source, built components or services, typically for vulnerabilities or policy compliance |
merge | Merges changes or fixes into source before a build |
build | Builds source, dependencies or data into a deployable artifact |
test | Verifies the functionality of a component or service |
deliver | Delivers a built artifact to repositories or storage |
deploy | Deploys a built artifact to target systems |
release | Releases a built, versioned artifact to a repository or distribution system |
clean | Removes unneeded tools, artifacts or data from workflow storage |
other | Anything that does not fit the above |
The guide says the list favors software CI and CD and that future versions may add types for other domains. A physical station such as "flash and fuse" has to use other, with the detail in name, description and properties.
steps and commands
A step has name, description, commands and properties. Each command has one meaningful field, executed: the text of the command that ran, ideally after interpolation. That is also where MBOMs leak secrets, so redact tokens before writing.
triggers and events
A trigger explains why a workflow or task ran. Required fields are type, bom-ref and uid. type is one of manual, api, webhook or scheduled. Other fields are name, description, resourceReferences, event, conditions, timeActivated, inputs, outputs and properties.
An event holds uid, description, timeReceived, data, source, target and properties. A condition holds description and expression, so you can record that a release only fires on a tag pattern. The guide notes that in event driven systems the event can arrive before the trigger fires, so recording both timestamps shows the gap.
workspaces and volumes
A workspace is a named, shareable storage area, usually a filesystem, used by tasks. Required fields are bom-ref and uid. Other fields are name, aliases, description, resourceReferences, accessMode (read-only, read-write, read-write-once, write-once, write-only), mountPath, managedDataType, volumeRequest, volume and properties.
A volume describes the backing storage: uid, name, mode (filesystem or block), path, sizeAllocated, persistent, remote and properties. Watch persistent: a workspace that survives between runs lets one build contaminate the next.
inputs, outputs and resource references
inputs is an array of inputType. Each input must carry at least one of resource, parameters, environmentVars or data, and may add source and target. parameters are name, value, dataType triples.
outputs is an array of outputType. Each output must carry one of resource, environmentVars or data, and has an optional type: artifact, attestation, log, evidence, metrics or other.
resource, source and target are each a resourceReferenceChoice: either a ref (a bom-ref in this BOM or a BOM-Link into another) or an externalReference with a URL, such as a CI log.
Field summary
| Object | Required in 1.7 schema | Fields that matter most for review |
|---|---|---|
| formula | none | components, services, workflows |
| workflow | bom-ref, uid, taskTypes | tasks, taskDependencies, trigger, resourceReferences, timeStart, timeEnd |
| task | bom-ref, uid, taskTypes | steps, inputs, outputs, workspaces |
| step | none | commands |
| command | none | executed |
| trigger | type, bom-ref, uid | event, conditions, timeActivated |
| event | none | timeReceived, source, data |
| workspace | bom-ref, uid | accessMode, volume |
| volume | none | persistent, remote, mode |
| inputType | one of resource, parameters, environmentVars, data | resource, parameters |
| outputType | one of resource, environmentVars, data | type, resource |
Declared versus observed formulation
The CycloneDX schema describes formulation as declaring "the precise steps to reproduce along with the observed formulas describing the steps which transpired." Those are two different documents in practice.
A declared MBOM is the recipe: the pipeline as written, the intended toolchain, the tests that must run. It fits the design or pre-build lifecycle phase. An observed MBOM is the record of one run, with real timestamps, digests, interpolated commands and output hashes, in the build phase.
Comparing the two is where the value is: a declared test stage that never ran, a drifted tool version, or a read-only workspace observed as read-write are the deviations the CycloneDX MBOM page says the capability should expose. Record which kind you are publishing with metadata.lifecycles, whose phases in the schema are design, pre-build, build, post-build, operations, discovery and decommission.
Keep the MBOM apart from the SBOM
The guide's main architectural advice is composability. Inventory (what is in the product) and formulation (how it was made) have different audiences and different sensitivity. A customer may be entitled to your SBOM but not to your build farm layout, your internal hostnames or your test rig configuration.
So the recommended pattern is two documents:
- The SBOM describes the product and its components.
- The MBOM describes the formula and workflows, in its own BOM with its own serial number.
- The SBOM's main component carries an external reference of type
formulationthat points at the MBOM, with a hash of the MBOM file so a consumer can confirm they received the right one.
The pointer can be a plain URL or a BOM-Link URN of the form urn:cdx:<serialNumber>/<version>. The guide compares this to splitting a VDR from an SBOM, and it gives each document its own lifecycle.
Worked example: an MBOM for a firmware build line
Here is a realistic, minimal scenario. A device maker ships sensor-hub-firmware 2.4.1 for a microcontroller board. A tag push starts a release pipeline that clones the repository, cross compiles with an ARM GCC toolchain inside a pinned builder container, runs hardware in the loop tests on a rig, then signs and publishes the binary with Sigstore. The MBOM records that run.
The document below validates against the CycloneDX 1.7 JSON schema (bom-1.7.schema.json, draft-07). The hashes are illustrative but full length, so they still match the schema's hash patterns. Hostnames use example.com.
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:6f1c2a7e-3d4b-4c5a-9e8f-1a2b3c4d5e6f",
"version": 1,
"metadata": {
"timestamp": "2026-10-20T09:14:00Z",
"lifecycles": [{ "phase": "build" }],
"component": {
"type": "firmware",
"bom-ref": "fw:sensor-hub@2.4.1",
"name": "sensor-hub-firmware",
"version": "2.4.1",
"hashes": [
{ "alg": "SHA-256", "content": "3b7e2f9a1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" }
]
}
},
"formulation": [
{
"bom-ref": "formula:sensor-hub@2.4.1",
"components": [
{
"type": "file",
"bom-ref": "src:sensor-hub@9f2c41e",
"name": "sensor-hub-source",
"version": "9f2c41e",
"externalReferences": [
{ "type": "vcs", "url": "https://git.example.com/devices/sensor-hub.git" }
]
},
{
"type": "application",
"bom-ref": "tool:arm-none-eabi-gcc@14.2.1",
"name": "arm-none-eabi-gcc",
"version": "14.2.1"
},
{
"type": "application",
"bom-ref": "tool:cmake@3.30.5",
"name": "cmake",
"version": "3.30.5"
},
{
"type": "container",
"bom-ref": "env:fw-builder@sha256-4d9a",
"name": "fw-builder",
"version": "2026.10.1",
"hashes": [
{ "alg": "SHA-256", "content": "4d9a7c1e2b3f405162738495a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7f" }
]
},
{
"type": "file",
"bom-ref": "out:sensor-hub.bin",
"name": "sensor-hub.bin",
"version": "2.4.1",
"hashes": [
{ "alg": "SHA-256", "content": "3b7e2f9a1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" }
]
}
],
"workflows": [
{
"bom-ref": "wf:release-2.4.1",
"uid": "ci-run-88121",
"name": "firmware release build",
"taskTypes": ["clone", "build", "test", "release"],
"trigger": {
"bom-ref": "trigger:tag-v2.4.1",
"uid": "ci-trigger-88121",
"type": "webhook",
"name": "tag push",
"timeActivated": "2026-10-20T09:02:11Z",
"event": {
"uid": "push-v2.4.1",
"description": "Tag v2.4.1 pushed to the release branch",
"timeReceived": "2026-10-20T09:02:10Z"
}
},
"tasks": [
{
"bom-ref": "task:checkout",
"uid": "ci-task-1",
"name": "checkout",
"taskTypes": ["clone"],
"steps": [
{ "name": "clone", "commands": [ { "executed": "git clone --depth 1 --branch v2.4.1 https://git.example.com/devices/sensor-hub.git" } ] }
],
"outputs": [ { "type": "artifact", "resource": { "ref": "src:sensor-hub@9f2c41e" } } ]
},
{
"bom-ref": "task:build",
"uid": "ci-task-2",
"name": "cross compile",
"taskTypes": ["build"],
"inputs": [
{ "resource": { "ref": "src:sensor-hub@9f2c41e" } },
{ "parameters": [ { "name": "CMAKE_BUILD_TYPE", "value": "Release" } ] }
],
"steps": [
{
"name": "configure and build",
"commands": [
{ "executed": "cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/arm-gcc.cmake -DCMAKE_BUILD_TYPE=Release" },
{ "executed": "cmake --build build --target sensor-hub.bin" }
]
}
],
"outputs": [ { "type": "artifact", "resource": { "ref": "out:sensor-hub.bin" } } ],
"timeStart": "2026-10-20T09:03:40Z",
"timeEnd": "2026-10-20T09:09:05Z"
},
{
"bom-ref": "task:hil-test",
"uid": "ci-task-3",
"name": "hardware in the loop tests",
"taskTypes": ["test"],
"inputs": [ { "resource": { "ref": "out:sensor-hub.bin" } } ],
"steps": [ { "name": "run test rig", "commands": [ { "executed": "ctest --test-dir build -L hil --output-on-failure" } ] } ],
"outputs": [ { "type": "log", "resource": { "externalReference": { "type": "log", "url": "https://ci.example.com/runs/88121/hil.log" } } } ]
},
{
"bom-ref": "task:sign-release",
"uid": "ci-task-4",
"name": "sign and publish",
"taskTypes": ["release"],
"inputs": [ { "resource": { "ref": "out:sensor-hub.bin" } } ],
"steps": [ { "name": "sign", "commands": [ { "executed": "cosign sign-blob build/sensor-hub.bin --bundle sensor-hub.bin.sigstore.json" } ] } ],
"outputs": [ { "type": "attestation", "resource": { "externalReference": { "type": "attestation", "url": "https://releases.example.com/sensor-hub/2.4.1/sensor-hub.bin.sigstore.json" } } } ]
}
],
"taskDependencies": [
{ "ref": "task:build", "dependsOn": ["task:checkout"] },
{ "ref": "task:hil-test", "dependsOn": ["task:build"] },
{ "ref": "task:sign-release", "dependsOn": ["task:hil-test"] }
],
"resourceReferences": [ { "ref": "env:fw-builder@sha256-4d9a" }, { "ref": "tool:arm-none-eabi-gcc@14.2.1" }, { "ref": "tool:cmake@3.30.5" } ],
"workspaces": [
{ "bom-ref": "ws:src", "uid": "ci-ws-1", "name": "source", "accessMode": "read-write", "mountPath": "/workspace" }
],
"timeStart": "2026-10-20T09:02:15Z",
"timeEnd": "2026-10-20T09:13:52Z"
}
]
}
]
}
Reading the example
- The subject.
metadata.componentis the firmware image, with the same SHA-256 as theout:sensor-hub.binoutput. That equality is the link an auditor checks first: the thing the MBOM says it built is the thing you shipped. The series post on tying a firmware SBOM to the exact image with a hash covers the same discipline for SBOMs. - The formula's components. The source tree, the cross compiler, CMake, the builder container and the output binary. The container carries its digest. The compiler does not, which is a weakness: in a real MBOM, pin the toolchain by hash as well, because a toolchain is the most valuable thing an attacker can tamper with.
- The trigger. A
webhooktrigger; a real release would add aconditionsentry for the tag rule. - Tasks and their types. Four tasks, each with one task type. The signing task uses
releasebecause there is nosignvalue in the enumeration. The hardware in the loop task usestest, which fits; a pure manufacturing station such as "program and fuse" would have to useother. - Inputs, outputs and graph. Build inputs mix a resource reference and a parameter; the test output is a
logand the signing output anattestation, both by URL.taskDependenciesrecords checkout, build, test, sign, andresourceReferencesnames the builder and tools for the whole run.
The SBOM that points to it
The product SBOM stays focused on what is inside the firmware and carries the pointer. This one also validates against CycloneDX 1.7:
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:0b8e9d2c-5a61-4f37-8c1d-7e2f3a4b5c6d",
"version": 1,
"metadata": {
"timestamp": "2026-10-20T09:15:00Z",
"lifecycles": [{ "phase": "build" }],
"component": {
"type": "firmware",
"bom-ref": "fw:sensor-hub@2.4.1",
"name": "sensor-hub-firmware",
"version": "2.4.1",
"hashes": [
{ "alg": "SHA-256", "content": "3b7e2f9a1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" }
],
"externalReferences": [
{
"type": "formulation",
"url": "urn:cdx:6f1c2a7e-3d4b-4c5a-9e8f-1a2b3c4d5e6f/1",
"comment": "MBOM for this build; access on request",
"hashes": [
{ "alg": "SHA-256", "content": "9c1f0e2d3b4a5968778695a4b3c2d1e0f9e8d7c6b5a4938271605f4e3d2c1b0a" }
]
}
]
}
},
"components": [
{
"type": "library",
"bom-ref": "pkg:github/FreeRTOS/FreeRTOS-Kernel@V11.1.0",
"name": "FreeRTOS-Kernel",
"version": "V11.1.0",
"purl": "pkg:github/FreeRTOS/FreeRTOS-Kernel@V11.1.0",
"licenses": [ { "license": { "id": "MIT" } } ]
}
],
"dependencies": [
{ "ref": "fw:sensor-hub@2.4.1", "dependsOn": ["pkg:github/FreeRTOS/FreeRTOS-Kernel@V11.1.0"] }
]
}
The URL is a BOM-Link to version 1 of the MBOM's serial number. The hash is the SHA-256 of the MBOM file as published, which you compute in step 5 of the walkthrough below. Someone who receives only the SBOM learns that an MBOM exists and how to verify it, without learning anything about your pipeline.
How MBOM relates to build provenance and the SPDX 3 Build profile
Three specifications describe "how this was built," and they are often confused. They are complementary rather than competing, and they differ mostly in scope and in how they are meant to be trusted.
SLSA provenance v1
SLSA provenance is an in-toto attestation with predicate type https://slsa.dev/provenance/v1. Its predicate has two parts. buildDefinition holds buildType, externalParameters, internalParameters and resolvedDependencies. runDetails holds builder (with id, builderDependencies and version), metadata (invocationId, startedOn, finishedOn) and byproducts.
The SLSA specification says the accuracy and completeness of the build definition are implied by runDetails.builder.id: you trust the claim because you trust the platform that produced it. Provenance describes one build as a black box with inputs and parameters, not every task inside.
If you want depth on generating and checking it, see software provenance attestation with SLSA v1 and how to validate SLSA provenance in CI.
in-toto attestations
in-toto is the envelope and statement format that SLSA provenance travels in. The in-toto attestation repository lists vetted predicate types including SLSA Provenance, CycloneDX, SPDX 2 and SPDX 3, Link, Release, Runtime Traces, Test Result and VULNS. That means an MBOM, being a CycloneDX BOM, can be wrapped in an in-toto statement whose subject is the firmware digest, and signed like any other attestation. The in-toto attestation framework walkthrough shows the envelope in detail.
The MBOM guide says the MBOM provides "detailed build provenance information required for SLSA attestations." Read that as a richer source you can derive provenance from, not a replacement for platform generated provenance.
The SPDX 3 Build profile
SPDX 3.0.1 has a Build profile that "defines the set of information required to describe an instance of a Software Build." It adds one class, Build, and relies on Core relationships to connect it to everything else.
Build properties, from the SPDX 3.0.1 model page:
| Property | Type | Cardinality |
|---|---|---|
buildType | anyURI | exactly 1 (required) |
buildId | string | 0 or 1 |
buildStartTime | DateTime | 0 or 1 |
buildEndTime | DateTime | 0 or 1 |
configSourceUri | anyURI | 0 or more |
configSourceDigest | Hash | 0 or more |
configSourceEntrypoint | string | 0 or more |
parameter | DictionaryEntry | 0 or more |
environment | DictionaryEntry | 0 or more |
The profile requires three relationship types from Core: hasInput, hasOutput and invokedBy. It allows hasHost, configures, ancestorOf, descendantOf and usesTool. All Build profile relationships are scoped to the build lifecycle period. The model page also notes that build start and end times are optional "to simplify creating reproducible builds," and that build logs can be identified with an external identifier of type urlScheme and the comment LogReference.
Here is the same firmware run expressed as an SPDX 3 Build. It validates against the SPDX 3.0.1 JSON schema (spdx-json-schema.json, JSON Schema 2020-12). JSON schema validation does not cover every SHACL constraint in the SPDX model, so run the SPDX tooling's full validation as well before you publish.
{
"@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
"@graph": [
{
"type": "CreationInfo",
"@id": "_:ci",
"specVersion": "3.0.1",
"created": "2026-10-20T09:15:00Z",
"createdBy": ["https://example.com/spdx/org/devices"]
},
{
"type": "Organization",
"spdxId": "https://example.com/spdx/org/devices",
"creationInfo": "_:ci",
"name": "Example Devices Inc."
},
{
"type": "SoftwareAgent",
"spdxId": "https://example.com/spdx/agent/ci-runner",
"creationInfo": "_:ci",
"name": "release pipeline runner"
},
{
"type": "Tool",
"spdxId": "https://example.com/spdx/tool/arm-none-eabi-gcc-14.2.1",
"creationInfo": "_:ci",
"name": "arm-none-eabi-gcc 14.2.1"
},
{
"type": "build_Build",
"spdxId": "https://example.com/spdx/build/ci-run-88121",
"creationInfo": "_:ci",
"build_buildType": "https://example.com/build-types/cmake-firmware/v1",
"build_buildId": "ci-run-88121",
"build_buildStartTime": "2026-10-20T09:02:15Z",
"build_buildEndTime": "2026-10-20T09:13:52Z",
"build_configSourceUri": ["https://git.example.com/devices/sensor-hub.git"],
"build_configSourceEntrypoint": [".ci/release.yml"],
"build_parameter": [
{ "type": "DictionaryEntry", "key": "CMAKE_BUILD_TYPE", "value": "Release" }
]
},
{
"type": "software_Package",
"spdxId": "https://example.com/spdx/pkg/sensor-hub-source-9f2c41e",
"creationInfo": "_:ci",
"name": "sensor-hub-source",
"software_packageVersion": "9f2c41e",
"software_downloadLocation": "https://git.example.com/devices/sensor-hub.git"
},
{
"type": "software_File",
"spdxId": "https://example.com/spdx/file/sensor-hub.bin",
"creationInfo": "_:ci",
"name": "sensor-hub.bin",
"software_primaryPurpose": "firmware",
"verifiedUsing": [
{ "type": "Hash", "algorithm": "sha256", "hashValue": "3b7e2f9a1c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7" }
]
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/rel/build-input",
"creationInfo": "_:ci",
"from": "https://example.com/spdx/build/ci-run-88121",
"relationshipType": "hasInput",
"to": ["https://example.com/spdx/pkg/sensor-hub-source-9f2c41e"],
"scope": "build"
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/rel/build-output",
"creationInfo": "_:ci",
"from": "https://example.com/spdx/build/ci-run-88121",
"relationshipType": "hasOutput",
"to": ["https://example.com/spdx/file/sensor-hub.bin"],
"scope": "build"
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/rel/build-invoked-by",
"creationInfo": "_:ci",
"from": "https://example.com/spdx/build/ci-run-88121",
"relationshipType": "invokedBy",
"to": ["https://example.com/spdx/agent/ci-runner"],
"scope": "build"
},
{
"type": "LifecycleScopedRelationship",
"spdxId": "https://example.com/spdx/rel/build-uses-tool",
"creationInfo": "_:ci",
"from": "https://example.com/spdx/build/ci-run-88121",
"relationshipType": "usesTool",
"to": ["https://example.com/spdx/tool/arm-none-eabi-gcc-14.2.1"],
"scope": "build"
}
]
}
SPDX 3 has no task graph, trigger or workspace object. You can nest builds with ancestorOf and descendantOf, but the shape is "a build is a node with edges." Its sbomType value build says when an SBOM was made, not how the product was made.
Comparison
| Aspect | CycloneDX formulation (MBOM) | SLSA provenance v1 | SPDX 3 Build profile |
|---|---|---|---|
| Unit of description | Formula with workflows, tasks, steps, commands | One build, as a black box | One Build element per build or stage |
| Who should produce it | Build tooling or scripts; can be hand authored | The build platform, so it is non-forgeable by tenants | Build tooling or SBOM generators |
| Trust model | Whatever you wrap it in (signature, attestation) | Signed by the builder; trust rests on builder.id | Whatever you wrap it in |
| Process detail | High: triggers, conditions, workspaces, volumes, per task inputs and outputs | Low: parameters and resolved dependencies | Medium: inputs, outputs, tool, host, parameters, environment |
| Non software processes | Intended (hardware, data, ML, certification), but task types are CI flavored | No | No, software builds only |
| Separate from inventory | Recommended (separate BOM linked by formulation reference) | Always separate | Same graph as the SBOM, or a separate document |
| Typical use | Detailed, shareable process record for audits and reproducibility | Machine verifiable claim that an artifact came from a given source and builder | Linking builds into an SPDX 3 graph alongside packages and files |
A sensible stack for a firmware vendor is platform generated SLSA provenance for every artifact, plus an MBOM for releases that a regulator or customer will review in depth.
Step by step: produce, validate, link and sign an MBOM
No single command produces a full MBOM. This workflow combines the official tools, as documented, with a little scripting.
1. Install the tools
The CycloneDX CLI README documents a Homebrew tap and a Docker image. cdxgen's README documents Homebrew and npm; from v13 the npm package is @cdxgen/cdxgen.
# CycloneDX CLI (validate, convert, diff, sign and verify BOMs)
brew install cyclonedx/cyclonedx/cyclonedx-cli
# or: docker run cyclonedx/cyclonedx-cli
# cdxgen (generate SBOMs, optional formulation section)
brew install cdxgen
# or: npm install -g @cdxgen/cdxgen --ignore-scripts
# jq for assembling JSON, cosign for signing
brew install jq cosign
2. Get a starting point from cdxgen
cdxgen's CLI documentation describes --include-formulation as "Generate formulation section with git metadata and build tools. Defaults to false." Its --spec-version flag defaults to 1.7. Run it at the root of the firmware repository:
cdxgen -t c --spec-version 1.7 --include-formulation -o sensor-hub.cdx.json .
# Look at what landed in formulation
jq '.formulation' sensor-hub.cdx.json | head -50
What you get is a useful seed (repository and commit facts, detected build tools), not a full record of your pipeline's tasks. Treat it as the formula's components list and add the workflow yourself. cdxgen's project types page lists c, cpp, cmake, conan, meson and vcpkg for C and C++, and says CMake projects are handled on a best effort basis without version numbers, so expect to fill in versions for vendored firmware libraries by hand.
3. Capture the observed run in CI
The most reliable MBOMs are written by the pipeline as it runs. This GitHub Actions fragment records one task; other CI systems differ only in variable names.
name: firmware-release
on:
push:
tags: ["v*"]
jobs:
build:
runs-on: ubuntu-24.04
container:
image: ghcr.io/example/fw-builder@sha256:4d9a7c1e2b3f405162738495a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c5d6e7f
steps:
- uses: actions/checkout@v4
- name: Build
run: |
START=$(date -u +%Y-%m-%dT%H:%M:%SZ)
cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/arm-gcc.cmake -DCMAKE_BUILD_TYPE=Release
cmake --build build --target sensor-hub.bin
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
HASH=$(sha256sum build/sensor-hub.bin | cut -d' ' -f1)
GCC_VER=$(arm-none-eabi-gcc -dumpversion)
jq -n \
--arg uid "ci-task-build-${GITHUB_RUN_ID}" \
--arg start "$START" --arg end "$END" \
--arg hash "$HASH" --arg gcc "$GCC_VER" \
'{task: {"bom-ref":"task:build", uid:$uid, name:"cross compile",
taskTypes:["build"], timeStart:$start, timeEnd:$end,
steps:[{name:"configure and build", commands:[
{executed:"cmake -B build -DCMAKE_TOOLCHAIN_FILE=cmake/arm-gcc.cmake -DCMAKE_BUILD_TYPE=Release"},
{executed:"cmake --build build --target sensor-hub.bin"}]}],
outputs:[{type:"artifact", resource:{ref:"out:sensor-hub.bin"}}]},
gcc:$gcc, hash:$hash}' > mbom-task-build.json
- uses: actions/upload-artifact@v4
with:
name: mbom-fragments
path: mbom-task-*.json
A final job merges the fragments into the workflow's tasks, fills taskDependencies from the job graph and sets a webhook trigger for the tag push. Record images by digest, and never write a command containing a secret.
4. Assemble and validate
Once the fragments are merged into a document like the example above, validate it. The CycloneDX CLI validate command accepts --input-version values from v1_0 to v1_7 (defaulting to v1.7) and --fail-on-errors to return a non-zero exit code.
cyclonedx validate \
--input-file sensor-hub-2.4.1.mbom.cdx.json \
--input-format json \
--input-version v1_7 \
--fail-on-errors
Schema validation rejects a taskTypes value such as flash or a workflow without uid, but it does not check that every ref resolves, that the graph is acyclic or that hashes are real. Add a check of your own:
# Every ref used in taskDependencies and resource references should exist as a bom-ref
jq -r '[.. | objects | ."bom-ref"? // empty] | sort | unique | .[]' \
sensor-hub-2.4.1.mbom.cdx.json > refs-defined.txt
jq -r '[.. | objects | .ref? // empty | strings] | sort | unique | .[]' \
sensor-hub-2.4.1.mbom.cdx.json > refs-used.txt
comm -13 refs-defined.txt refs-used.txt # anything printed is a dangling ref
5. Hash the MBOM and link it from the SBOM
MBOM_SHA=$(sha256sum sensor-hub-2.4.1.mbom.cdx.json | cut -d' ' -f1)
MBOM_SERIAL=$(jq -r '.serialNumber | sub("^urn:uuid:"; "")' sensor-hub-2.4.1.mbom.cdx.json)
MBOM_VERSION=$(jq -r '.version' sensor-hub-2.4.1.mbom.cdx.json)
jq --arg url "urn:cdx:${MBOM_SERIAL}/${MBOM_VERSION}" --arg sha "$MBOM_SHA" \
'.metadata.component.externalReferences += [{
type: "formulation", url: $url,
hashes: [{alg: "SHA-256", content: $sha}]
}]' sensor-hub.cdx.json > sensor-hub-2.4.1.sbom.cdx.json
cyclonedx validate --input-file sensor-hub-2.4.1.sbom.cdx.json --input-version v1_7 --fail-on-errors
If cdxgen already placed a formulation section inside sensor-hub.cdx.json, decide whether you want it there. If the SBOM goes to customers and the formulation contains internal detail, strip it from the SBOM copy with jq 'del(.formulation)' before you add the link.
6. Sign the MBOM
The Sigstore documentation shows cosign sign-blob <file> --bundle bundle.sigstore.json for keyless signing, with a --key variant for your own key. The bundle holds the signature, certificate and transparency log proof.
cosign sign-blob sensor-hub-2.4.1.mbom.cdx.json \
--bundle sensor-hub-2.4.1.mbom.sigstore.json
# A consumer verifies against the identity they expect to have signed it
cosign verify-blob sensor-hub-2.4.1.mbom.cdx.json \
--bundle sensor-hub-2.4.1.mbom.sigstore.json \
--certificate-identity "https://github.com/example/sensor-hub/.github/workflows/firmware-release.yml@refs/tags/v2.4.1" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com"
The CycloneDX CLI also has sign and verify commands: cyclonedx sign bom signs the whole BOM document with an RSA private key, and cyclonedx sign file writes a detached PKCS1 RSA SHA256 signature for any file. Use whichever method your consumers can check.
7. Store, classify and share
Publish the SBOM widely and the MBOM narrowly. In CycloneDX 1.7, set metadata.distributionConstraints.tlp on the MBOM so its handling rule travels with it. Store each MBOM next to its release artifacts, keyed by artifact hash, for as long as the product is supported.
Who consumes an MBOM
Auditors and certification bodies
An auditor wants evidence that the declared process ran: scans produced output, tests ran before release, signing happened on the controlled builder. The guide lists compatibility goals with NIST SSDF and SLSA, ISO 9001 and IPC standards, FIPS 140 and Common Criteria, ISO 26262 and ISO/SAE 21434, and FDA 21 CFR Part 11, ISO 13485 and IEC 62304. None of those names the CycloneDX MBOM as a required artifact; it packages evidence they already ask for.
Device regulators
The US FDA's current premarket cybersecurity guidance, "Cybersecurity in Medical Devices: Quality Management System Considerations and Content of Premarket Submissions," is dated February 2026 and supersedes the June 2025 version. It addresses section 524B of the FD&C Act for cyber devices. Device makers know it mainly for its SBOM expectations, covered in the series posts on FDA SBOM requirements under section 524B and HBOM in regulated industries.
Be precise about what that means for MBOMs. We found no mention of a CycloneDX MBOM in the FDA's guidance materials, and the guidance's title places cybersecurity inside quality management system considerations, which is about controlled, documented processes in general. An MBOM is one machine readable way to show how a given firmware release was built and tested, and it fits naturally next to the SBOM in a submission package. Treat it as supporting evidence, not as a stated FDA requirement.
Buyers and downstream integrators
A buyer wants to know whether the release was built on a controlled builder, from the tagged source, with the stated toolchain, and tested. A redacted MBOM answers that without exposing internal hostnames. The hardware and firmware supply chain inventory post covers the hardware side.
Incident responders
When a toolchain or build image turns out to be compromised, the first question is which releases it touched. MBOMs that pin the builder and compiler by digest turn that into a query over stored files instead of a hunt through expired CI logs.
Your own engineers
An MBOM with exact tool versions, parameters and commands is most of what someone needs to rebuild a two year old release. The reproducible builds post covers making that rebuild bit for bit identical.
| Consumer | Main question | Fields they look at |
|---|---|---|
| Auditor | Did the declared controls run? | taskTypes, task outputs of type log and evidence, timeStart, timeEnd |
| Device regulator reviewer | Is the build process controlled and documented? | Formula components, trigger, test tasks, signing task, link to SBOM |
| Buyer | Was this release built from the stated source on a controlled builder? | Source component, builder digest, output hash, attestation output |
| Incident responder | Which releases used the bad tool or image? | Formula components hashes, resourceReferences |
| Engineer | Can I rebuild this exactly? | commands, parameters, environmentVars, toolchain versions |
Honest limits
- Little tooling. cdxgen can seed a formulation section and the CycloneDX CLI validates it. Beyond that, most MBOMs are built by pipeline scripts or written by hand. Expect to assemble the workflow, task and step detail yourself from what your CI system exposes.
- Software shaped task types. The
taskTypesenumeration is CI oriented. Physical manufacturing steps fall intoother, and the guide says more types may come later. - No built in trust. A compromised pipeline can write a clean looking MBOM, which is why platform generated SLSA provenance still matters.
- Shallow schema checks. References, graph shape and hashes are not validated.
- Sensitive by nature. Commands, hostnames and paths help an attacker, so redaction and access control are part of the job.
- Few consumers today. Tools built to ingest SBOMs are mainly built around components, dependencies and vulnerabilities. Do not assume a recipient's tooling will display or check
formulation; ask before you rely on it.
Where Safeguard fits
Safeguard's fit here is indirect, and it is worth saying plainly. Safeguard does not generate MBOMs or CycloneDX formulation sections, and it does not generate HBOMs. What it does is the work around the MBOM. SBOM generation and analysis is generally available, producing SPDX (JSON, XML, RDF) and CycloneDX (JSON, XML) with more than 30 export formats including CSV, Excel and PDF, and continuous scanning on push, on a schedule or on release. That is the product inventory your MBOM's formulation reference hangs off.
Attestation and signing is also generally available: SLSA, Sigstore keyless signing with Fulcio and Rekor, in-toto and X.509, with Kubernetes admission control. That covers the provenance half of the "how was it built" question. Third party risk management, generally available, lets you onboard suppliers and request SBOMs, projects or products from them, which is how a buyer would collect supplier documents in the first place. Policies, gates and guardrails, generally available, can block a release that is missing the SBOM or attestation your process requires. The Trust Center for publishing documents to customers is in early access.
If MBOMs become a supplier requirement, produce them in your own pipeline and keep them as linked, signed files next to the SBOMs you manage.
Common mistakes
- Saying "MBOM" without saying which one. Ask for "a CycloneDX BOM with a formulation section, version 1.5 or later," or a hardware supplier will send an ERP export.
- Putting the MBOM inside the customer SBOM. It pushes process detail to everyone. Separate it and link it.
- Recording tools by name only. Record the digest of the toolchain archive or builder image.
- Calling a declared recipe observed. Mark the lifecycle phase honestly and use real timestamps.
- Interpolated secrets in
executed. Scrub tokens before the MBOM is written. - Skipping validation, or trusting it too much. Run
cyclonedx validate --fail-on-errors, then check references separately. - Unsigned MBOMs. An unsigned document from a compromised pipeline proves nothing.
- No link back to the artifact. If the output hash does not match the shipped file, the MBOM describes some other build.
FAQ
What does MBOM stand for?
Manufacturing bill of materials. In manufacturing ERP and PLM systems it is the parts and routing structure a factory uses to build a product. In CycloneDX it is a BOM that uses the formulation property to describe how a component, service or the BOM itself was made.
What is the difference between an MBOM and an EBOM?
In manufacturing, an EBOM describes the product as designed and an MBOM the product as built, structured around operations. CycloneDX does not define an EBOM; its MBOM is about process.
Which CycloneDX version added MBOM support?
Version 1.5, tagged on 26 June 2023, added the formulation property and its workflow, task, step, trigger and workspace objects. Versions 1.6 and 1.7 keep it, and 1.7 widens the description to any referencable object in the BOM.
Is an MBOM the same as SLSA provenance?
No. SLSA provenance is a compact claim, produced by the build platform, about one build's inputs, parameters and builder. An MBOM describes the full process in more detail but carries no trust unless you sign it.
Does SPDX have an MBOM?
Not by that name. SPDX 3.0.1 has a Build profile: a Build class with hasInput, hasOutput, invokedBy and usesTool relationships, but no task, step, trigger or workspace objects.
Can I generate an MBOM automatically?
Partly. cdxgen's --include-formulation flag adds a formulation section with git metadata and build tools. The detailed workflow, tasks and commands usually come from scripts in your CI pipeline that write JSON as each job runs.
Should the MBOM be in the same file as the SBOM?
The OWASP CycloneDX Authoritative Guide to MBOM recommends a separate document, referenced from the SBOM with an external reference of type formulation and a hash. That lets you give the SBOM to customers while restricting the MBOM.
Does the FDA require an MBOM for medical devices?
Not as a named document. The FDA's February 2026 premarket cybersecurity guidance addresses section 524B for cyber devices and is known for its SBOM expectations; we found no mention of a CycloneDX MBOM in it. An MBOM can serve as supporting evidence of a controlled build process, alongside the SBOM.
Can a CycloneDX MBOM describe hardware manufacturing?
The guide says formulation is meant to describe hardware manufacturing, assembly and quality control too. In practice the taskTypes enumeration is software oriented, so hardware steps use other with detail in names, descriptions and properties.
How do I validate an MBOM?
Run cyclonedx validate --input-file <file> --input-version v1_7 --fail-on-errors with the CycloneDX CLI. Then check that every ref resolves to a bom-ref and that output hashes match the shipped artifacts, since schema validation does not cover those.
Fix it without waiting for upstream
An MBOM tells you how a firmware image was built, but if the FreeRTOS kernel or a library inside it carries a known vulnerability, the record of a clean build does not make the image safe. Someone still has to move the dependency to a fixed version and ship a new build, and that part of software supply chain hygiene applies to the open source you compile into firmware just as it does to a web service.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
None of these demos produce an MBOM; Safeguard does not generate formulation data. They show the parts of the workflow around it: scanning what you build, fixing what the scan finds with a person approving each step, and querying the results from an assistant. Download the CLI from https://safeguard.sh/download and read the setup guide at https://docs.safeguard.sh/docs/cli if you want to try them.
The first demo is a real CLI session. After install, safeguard opens the interactive terminal, you ask it to fix the lodash CVE in a repo, and it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, prints a summary and pushes nothing. On a firmware project the dependency and the build commands differ, but the loop is the same one your MBOM would later record as a merge, build and test.
Click through it at your own pace, or open it full screen.
The second demo sets up the runner on your own machine, approves it with its device code, then scans from the terminal. For build lines that run on internal infrastructure, this is how scanning stays on hardware you control, next to the builder that the MBOM describes.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask about the projects and vulnerabilities behind a firmware release in plain language, which is the inventory side of the question an MBOM answers about process.
Click through it at your own pace, or open it full screen.
Quick check
Your MBOM validates against the CycloneDX 1.7 schema. Does that prove every task dependency points at a real task?
No. The JSON schema checks structure, required fields and enumerations, such as the allowed taskTypes values. It does not check that a ref in taskDependencies matches an existing bom-ref, that the task graph is acyclic or that recorded hashes match real files. Check those with a separate script.
A customer asks for your SBOM, and your MBOM contains internal hostnames and build commands. What does the CycloneDX MBOM guide recommend?
Keep the MBOM in its own BOM document and reference it from the SBOM's main component with an external reference of type formulation, plus a hash. The customer receives the SBOM and the pointer, and you grant access to the MBOM separately. In CycloneDX 1.7 you can also mark the MBOM with a TLP classification in metadata.distributionConstraints.
Sources
- OWASP CycloneDX: Manufacturing Bill of Materials (MBOM) capability page
- OWASP CycloneDX: Authoritative Guide to MBOM, first edition (21 October 2025), PDF
- CycloneDX 1.7 JSON schema (bom-1.7.schema.json)
- CycloneDX 1.6 JSON schema (bom-1.6.schema.json)
- CycloneDX 1.5 JSON schema (bom-1.5.schema.json)
- CycloneDX 1.4 JSON schema (bom-1.4.schema.json)
- CycloneDX specification release 1.5 on GitHub
- CycloneDX specification release 1.6 on GitHub
- CycloneDX specification release 1.7 on GitHub
- Ecma International: ECMA-424
- CycloneDX CLI README
- cdxgen README
- cdxgen CLI documentation
- SPDX 3.0.1: Build profile
- SPDX 3.0.1: Build class
- SPDX 3.0.1: RelationshipType vocabulary
- SPDX 3.0.1: SbomType vocabulary
- SPDX 3.0.1 JSON schema
- SLSA: Provenance v1.0
- in-toto attestation framework repository
- in-toto attestation: vetted predicates
- Sigstore: Signing blobs with cosign
- US FDA: Cybersecurity in Medical Devices: Quality Management System Considerations and Content of Premarket Submissions (February 2026)
- US FDA: Cybersecurity (Digital Health Center of Excellence)
Last reviewed: 24 October 2026. CycloneDX formulation and the MBOM guide are still evolving; check the CycloneDX specification repository and the MBOM capability page for changes after this date.