Safeguard
Supply Chain Security•

How to Create an HBOM in CycloneDX, Step by Step (Full JSON Example)

Build a device HBOM in CycloneDX 1.7 JSON: device and firmware components, part properties, links to firmware SBOMs, then validate and sign it.

Safeguard Security Team
39 min read

To create an HBOM in CycloneDX, write a CycloneDX JSON document whose metadata.component is your product with type set to device, nest each board, chip and module under it as further device components, add firmware as separate firmware or operating-system components, and record part details in properties. Then validate it against the official schema with the CycloneDX CLI.

This guide builds a complete HBOM for a sample industrial gateway, field by field, using only fields defined in the official CycloneDX 1.7 JSON schema. We validated the finished file with the official cyclonedx CLI (v0.33.1) and with a Python JSON Schema validator against the published schema; both reported zero errors. We also show how to sign it, how to link it to firmware SBOMs, where the format still has gaps, and what an SPDX 3 version looks like today.

Key takeaways

  • CycloneDX has no separate HBOM format. An HBOM is an ordinary CycloneDX BOM in which the main components have type device. The schema says a device with firmware "SHOULD" be modelled as a hardware component plus a separate firmware or operating-system component.
  • Part numbers go in name, revisions in version, the maker in manufacturer and the seller in supplier. Both organization fields accept a postal address with a country since CycloneDX 1.6.
  • Reference designators, quantities, lot numbers, SKUs and MAC addresses have registered property names in the official cdx:device taxonomy. Country of fabrication, assembly and test location and technology node do not, so use a namespace you control and document it for the recipient.
  • Link firmware SBOMs with an external reference of type bom, either as a BOM-Link URN (urn:cdx: plus the SBOM's serial number and version) or as a URL with a SHA-256 hash.
  • Validate with cyclonedx validate --input-version v1_7 --fail-on-errors. Schema validation catches misspelled fields but not misspelled property names, because properties are free-form name and value pairs.
  • In our test, cyclonedx sign bom refused a JSON BOM ("Only XML BOMs are currently supported for signing"). Sign the JSON file with a detached signature instead.
  • SPDX 3.0.1 can mark a package as device or firmware, but it has no released hardware profile and no address or country field. A Hardware profile exists only on the development branch of the SPDX 3 model.

What is an HBOM in CycloneDX terms?

A hardware bill of materials lists the physical parts in a product: boards, processors, memory, radios, security chips, connectors and the firmware that runs on them, with enough detail to identify each part and where it came from. If you need the background first, our posts on HBOM versus SBOM and on inventorying hardware and firmware from chips to boards cover the why. This post is the how.

CycloneDX describes its HBOM capability as a way to "inventory hardware components for IoT, ICS, and other types of embedded and connected devices." There is no HBOM schema, no HBOM file extension and no HBOM flag. An HBOM is a CycloneDX BOM that uses the hardware parts of the object model. The schema's own definition of an external reference of type bom lists "SBOM, OBOM, HBOM, SaaSBOM, etc" side by side, which tells you how the project thinks about it: one format, many kinds of inventory.

So any CycloneDX tool can parse, merge and diff your HBOM, but no generic tool knows which fields matter for hardware. You have to choose them deliberately.

The CycloneDX lifecycles field even anticipates this mix. Its definition of the operations phase says such a BOM "will generally encompass multiple SBOMs describing the applications and operating system, along with HBOMs describing the hardware that makes up the system."

Which CycloneDX version should you use for an HBOM?

Use 1.7 unless a recipient's tooling only accepts 1.6. The CycloneDX specification overview lists 1.7 as the current version, released on 21 October 2025, and lists the Ecma International standard ECMA-424 with a publication date of 10 December 2025. The specification repository has since tagged maintenance releases 1.7.1 (2 June 2026) and 1.7.2 (17 September 2026). The official CLI's validate command defaults to 1.7.

For hardware, the two versions are almost identical. We compared the 1.6 and 1.7 JSON schemas directly. The component types are the same thirteen, including device, device-driver, firmware and operating-system, and the organization and address structures are unchanged. The differences that touch an HBOM are small.

Field or featureCycloneDX 1.6CycloneDX 1.7Why it matters for an HBOM
component.type value deviceYesYesThe core of any HBOM
component.manufacturer (organization)YesYesThe maker of each part
postalAddress.country on manufacturer and supplierYesYesCountry of the organization, not of the part
metadata.lifecyclesYesYesSays when the inventory was captured
metadata.manufactureDeprecatedDeprecatedUse metadata.component.manufacturer instead
metadata.distributionConstraints with a TLP labelNoYesMarks supplier data as AMBER or RED for sharing
component.isExternalNoYesMarks parts expected from the environment, not shipped in the box
externalReference.propertiesNoYesLets you annotate a link, for example to a firmware SBOM
Hash algorithms1214 (adds Streebog-256 and Streebog-512)Firmware image hashes

One trap: if you add a 1.7 only field and then change specVersion to 1.6, validation fails. We tried it with the example below. The 1.7 file passed, and the same file labelled 1.6 failed because distributionConstraints does not exist in 1.6. Remove 1.7 only fields before you down-convert.

Which CycloneDX fields carry hardware data?

Everything in this table is defined in the official CycloneDX 1.7 JSON schema or in the official property taxonomy. Nothing here is a made up extension.

What you need to recordCycloneDX fieldDefined in
The finished productmetadata.component with type deviceSchema
Sub-assemblies, chips, modulesNested components with type deviceSchema
Firmware, bootloader, OS imageComponents with type firmware or operating-systemSchema
Drivers shipped with the deviceComponents with type device-driverSchema
Manufacturer part numbernameSchema (CISA's own mapping)
Hardware revision or steppingversionSchema
Who made the partmanufacturer (name, address, url, contact)Schema
Who sold it to yousupplierSchema
Datasheet, security contactexternalReferences of type documentation, rfc-9116Schema
Firmware image digesthashes with alg and contentSchema
Reference designator on the boardcdx:device:location propertyTaxonomy
Quantity per unitcdx:device:quantity propertyTaxonomy
Function (memory, network, storage)cdx:device:function propertyTaxonomy
Package or mounting typecdx:device:deviceType propertyTaxonomy
Lot or batchcdx:device:lotNumber propertyTaxonomy
Internal SKUcdx:device:sku propertyTaxonomy
Radio certification IDcdx:device:certifications:US:FCC:id style propertyTaxonomy
MAC addresscdx:device:macAddress propertyTaxonomy
Assembly treedependencies with dependsOnSchema
How complete the inventory iscompositions with aggregateSchema
Link to a firmware SBOMexternalReferences of type bomSchema

The cdx:device namespace is administered by the CycloneDX Core Working Group. Its taxonomy also defines cdx:device:serialNumber, cdx:device:prodTimestamp, GS1 identifiers such as cdx:device:gs1:gtin-13, and links to an engineering or manufacturing BOM (cdx:device:bom:ebom and cdx:device:bom:mbom).

How do CISA's HBOM fields map to CycloneDX today?

The CISA Hardware Bill of Materials Framework for Supply Chain Risk Management, published on 25 September 2023, defines a taxonomy of field names such as COMP_MANUFACTURER_PN and COMP_MFG_LOC_CODE. Our hardware and software BOM post for IoT devices touches on it; here we only need its mapping. For each field, Appendix C lists an "Equivalent CycloneDX field". Most say "None."

That mapping was written before CycloneDX 1.6 added a manufacturer organization with a postal address on every component, and before the device property taxonomy grew lot numbers and quantities. Several gaps can now be filled with defined fields. The CISA document also tells you what to do when no field exists: "the developer should create a user-defined field to provide the information to the requestor," and the taxonomy behind it "needs to be clearly communicated" to the recipient.

CISA fieldCISA's 2023 CycloneDX mappingWhat we used in CycloneDX 1.7
FGA_NUM (product number)namemetadata.component.name
FGA_VERSIONversionmetadata.component.version
FGA_SUPPLIER (finished good OEM)Nonemetadata.component.supplier
FGA_MAIN_MANUFACTURERSupplier, publishermetadata.component.manufacturer
FGA_MAIN_LOC_CODE (assembly location)NoneCustom property, ISO 3166-2 code
COMP_MANUFACTURER_PNnameComponent name
COMP_MANUFACTURERSupplier, publisherComponent manufacturer
COMP_SUPPLIER (distributor)NoneComponent supplier
COMP_MFG_LOC_CODENoneCustom property (the address on manufacturer is the company's, not the fab's)
ASSY_AND_TEST_LOC_CODENoneCustom property
TECHNOLOGY_NODENoneCustom property
QUANTITYNonecdx:device:quantity
COMP_DATECODE (lot or date code)Nonecdx:device:lotNumber
COMP_DATASHEETNoneexternalReferences type documentation
COMP_HASHHashhashes (firmware components)
HBOM_AUTHORmetadata/authors/authormetadata.authors
HBOM_MODIFY_DATEmetadata/timestampmetadata.timestamp

The country question needs care. CISA itself warns that a single "Country of Origin" field in another format cannot map one to one onto its several location fields: headquarters, main manufacturing site, alternate site, component fab, and assembly and test site. A CycloneDX manufacturer.address.country is the country of the organization's address. It is not the country where the die was fabricated or the board assembled. If your buyer asks for fab or assembly location, record it explicitly and say which one it is.

Before you start: what to collect

An HBOM is only as good as the records behind it. Before writing any JSON, gather:

  1. The engineering BOM from your PLM or CAD system: every part number, reference designator and quantity on each board.
  2. The approved vendor list, which tells you who manufactures each part and which distributor you buy from. These are different organizations more often than not.
  3. Supplier declarations for fab, assembly and test locations, if your customers ask for them. Manufacturer datasheets rarely carry this.
  4. Firmware build outputs: the exact image files that ship, so you can hash them, and the SBOM your firmware build produces.
  5. Module documentation for anything you buy as a finished module, such as a Wi-Fi card. You usually cannot see inside it, and your HBOM should say so.

Decide the scope too. CISA's framework separates a hierarchical HBOM per assembly level and recommends enough granularity that "key information" is not "hidden at the assembly/kit level." For security work, the parts that matter most are those that run code or hold keys: processors, microcontrollers, radios, storage, security chips and anything with updatable firmware. Passive parts can be added later if procurement needs them. Our post on firmware below the operating system explains why the code carrying components deserve first attention.

Step by step: build the HBOM

Our sample product is a fictional "EG-200 Edge Gateway" from a fictional "Example Devices Inc." Every company, part number and identifier in it is invented and uses example.com addresses. The structure is what you would use for a real device.

Step 1: install the official CycloneDX CLI

The CycloneDX CLI is the project's own tool for validating, converting, merging, diffing and signing BOMs. Download it from the official GitHub releases page. We used v0.33.1, published on 23 July 2026, which ships self contained binaries for Linux, macOS and Windows.

# macOS on Apple silicon (use cyclonedx-osx-x64 on Intel Macs)
curl -fsSL -o cyclonedx \
  https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.33.1/cyclonedx-osx-arm64
chmod +x cyclonedx
shasum -a 256 cyclonedx
./cyclonedx --version
# Linux x64
curl -fsSL -o cyclonedx \
  https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.33.1/cyclonedx-linux-x64
chmod +x cyclonedx
sha256sum cyclonedx
./cyclonedx --version
# Windows x64
Invoke-WebRequest -Uri https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.33.1/cyclonedx-win-x64.exe -OutFile cyclonedx.exe
Get-FileHash .\cyclonedx.exe -Algorithm SHA256
.\cyclonedx.exe --version

The project README also documents a Homebrew tap (brew install cyclonedx/cyclonedx/cyclonedx-cli) and a Docker image (docker run cyclonedx/cyclonedx-cli). The v0.33.1 release page we fetched listed only the binaries, with no checksum or signature files. So record the SHA-256 of the binary you downloaded the first time and pin it in CI. For the macOS arm64 binary we downloaded, it was 750c148780154833f6401f9067d08c5a4c31567b6ee3c26c062c3a95c62d741c, and --version printed 0.33.1+b3cfa4b0.

Step 2: write the BOM header

Every CycloneDX JSON BOM needs bomFormat and specVersion; those are the only two required top level fields. Add a serialNumber in urn:uuid: form (the schema enforces an RFC 4122 pattern), a version that you increment each time you reissue the same BOM, and a metadata block.

{
  "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
  "bomFormat": "CycloneDX",
  "specVersion": "1.7",
  "serialNumber": "urn:uuid:6a6f5133-73ce-41c4-b881-829f3f9ea5fd",
  "version": 1,
  "metadata": {
    "timestamp": "2026-10-23T09:00:00Z",
    "lifecycles": [{"phase": "post-build"}],
    "authors": [{"name": "Hardware Compliance Team", "email": "hbom@example.com"}],
    "distributionConstraints": {"tlp": "AMBER"}
  }
}

Generate the UUID with uuidgen on macOS or Linux, or [guid]::NewGuid() in PowerShell, and convert it to lower case: the schema pattern only accepts lower case hex digits.

Three choices in this header matter for hardware:

  • Lifecycle. The schema defines design, pre-build, build, post-build, operations, discovery and decommission. An HBOM drawn from the engineering BOM before production is design. One that describes units as built and shipped, with real lot numbers and firmware hashes, is post-build. One that describes a deployed fleet is operations.
  • Authors versus manufacturer. metadata.manufacturer is defined as "the organization that created the BOM," not the device maker. The device maker goes on metadata.component.manufacturer. The older metadata.manufacture field is deprecated in both 1.6 and 1.7.
  • Distribution constraints. HBOMs often contain supplier and location data you do not want forwarded. CycloneDX 1.7 lets you label the whole BOM with a Traffic Light Protocol value: CLEAR, GREEN, AMBER, AMBER_AND_STRICT or RED.

Step 3: describe the finished device

The product itself goes in metadata.component. Give it a bom-ref so the dependency graph can point at it.

{
  "type": "device",
  "bom-ref": "eg200",
  "manufacturer": {"name": "Example Devices Inc.", "address": {"country": "US"}, "url": ["https://example.com"]},
  "name": "EG-200 Edge Gateway",
  "version": "hw-rev-C",
  "properties": [
    {"name": "cdx:device:sku", "value": "EG200-C-US"},
    {"name": "cdx:device:certifications:US:FCC:id", "value": "EXAMPLE-EG200C"}
  ]
}

Use version for the hardware revision, not the firmware version. Firmware gets its own components in Step 7. Mixing the two is the most common reason an HBOM stops matching reality after the first over the air update.

The cdx:device:certifications pattern in the taxonomy takes an ISO 3166-1 alpha-2 country code, then the authority abbreviation, then id or url. So a US FCC ID is cdx:device:certifications:US:FCC:id.

Step 4: model the assembly tree with nested components

CycloneDX components can contain components, which gives you the parent and child structure CISA describes as a hierarchical BOM. Our gateway has one main board assembly, and the chips and modules sit inside it.

{
  "type": "device",
  "bom-ref": "eg200-mainboard",
  "name": "EG200-MB",
  "version": "rev-3",
  "description": "Main board assembly (PCBA)",
  "manufacturer": {"name": "Example Contract Manufacturing S.A.", "address": {"country": "MX"}},
  "components": []
}

Here the manufacturer of the board is the contract manufacturer that assembles it, which matches CISA's example of an OEM using a contract manufacturer. The OEM stays the manufacturer of the finished product.

If a product has several sub-assemblies (a power supply, a display, a daughter board), give each its own nested device component. If a sub-assembly is complex or comes from another company, you can instead describe it in its own HBOM and link to it, the same way Step 8 links firmware SBOMs.

Step 5: add part level detail with registered properties

For each chip, put the manufacturer part number in name, the stepping or revision in version, and identify both the manufacturer and the supplier when they differ.

{
  "type": "device",
  "bom-ref": "soc",
  "name": "EXS-A55Q-1G4",
  "version": "B1",
  "supplier": {"name": "Example Components Distribution LLC", "address": {"country": "US"}},
  "manufacturer": {"name": "Example Silicon Ltd.", "address": {"country": "TW"}},
  "properties": [
    {"name": "cdx:device:function", "value": "microprocessor"},
    {"name": "cdx:device:location", "value": "U1"},
    {"name": "cdx:device:deviceType", "value": "SMD"},
    {"name": "cdx:device:quantity", "value": "1"},
    {"name": "cdx:device:lotNumber", "value": "L2614A07"}
  ]
}

The schema's description of supplier is the reason to keep both: the supplier "may often be the manufacturer, but may also be a distributor or repackager." That difference is exactly what a counterfeit parts review looks at.

Property values are always strings, even for numbers. A property has a required name and an optional value, and duplicate names are allowed. That last rule is useful when one part number appears in several board positions: you can repeat cdx:device:location, or record the locations once and the total in cdx:device:quantity. Pick one convention and document it.

Step 6: add origin data with your own namespace

There is no registered CycloneDX property for the country where a chip was fabricated, the assembly and test location, or the process node. Use a namespace you control. In the example we used exampledevices:hbom: followed by CISA's own field names, so the recipient can map them back to the framework:

[
  {"name": "exampledevices:hbom:compMfgLocCode", "value": "TW-HSZ"},
  {"name": "exampledevices:hbom:assyAndTestLocCode", "value": "MY-10"},
  {"name": "exampledevices:hbom:technologyNode", "value": "12 nm"}
]

The values are ISO 3166-2 subdivision codes, which is what CISA's *_LOC_CODE fields call for. A few rules from the official taxonomy README apply:

  • Do not invent names under cdx:. The README says unofficial names "MUST NOT be used under the cdx top-level namespace."
  • Do not use internal: in a BOM you share. That namespace is for internal use only, and BOMs shared with third parties "SHOULD NOT" include it.
  • Namespace segments are separated by single colons, should be lower case, and may only use letters, digits, hyphens, underscores and spaces.

Whatever you choose, send the recipient a short table of your custom names and their meaning. CISA's framework asks for the same.

Step 7: add firmware as separate components

The schema's definition of the device type is explicit: a hardware device containing firmware "SHOULD include a component for the physical hardware itself and another component of type 'firmware' or 'operating-system' (whichever is relevant)."

In the example, firmware appears in two places:

  • Firmware inside a bought in part is nested inside that part's component. The TPM and the Wi-Fi module each contain a firmware child, supplied by the part maker.
  • Firmware you build and ship sits at the top level of components: the bootloader (firmware) and the embedded Linux image (operating-system).

Hash every firmware image you ship. On Linux run sha256sum eg200-os-4.2.1.img, on macOS shasum -a 256 eg200-os-4.2.1.img, and on Windows Get-FileHash eg200-os-4.2.1.img -Algorithm SHA256, then put the hex string in hashes with alg set to SHA-256. When a unit in the field reports a firmware hash that is not in any of your HBOMs, you know something changed that your records do not explain.

Step 8: link firmware SBOMs

An HBOM should not try to list every library inside the firmware. That is the firmware SBOM's job. Link to it with an external reference of type bom. CycloneDX gives you two ways, and the example uses both.

The first is BOM-Link, a URN registered with IANA. Its syntax is urn:cdx: followed by the target BOM's serial number (without the urn:uuid: prefix), a slash, the BOM version, and optionally a # and a bom-ref to point at one component inside it:

{
  "type": "bom",
  "url": "urn:cdx:66409f65-4644-4aca-9289-844f92fc2168/1#bootloader",
  "comment": "Bootloader SBOM in the EG-200 firmware SBOM"
}

For that link to resolve, the firmware SBOM must carry "serialNumber": "urn:uuid:66409f65-4644-4aca-9289-844f92fc2168", "version": 1, and a component with "bom-ref": "bootloader". BOM-Link identifies a document; it does not say where to download it. Pair it with a plain URL when the recipient needs to fetch the file, and add a hash so they can check they got the right one:

{
  "type": "bom",
  "url": "https://example.com/eg200/sbom/eg200-os-4.2.1.cdx.json",
  "hashes": [{"alg": "SHA-256", "content": "b52b57c18fc9eea8dd59d3c9044805b46880f6cd596a20b25748ee5dc9463c35"}]
}

Keeping the HBOM and the firmware SBOM as separate documents also follows the CycloneDX BOM-Link guidance on "maintaining separation of concerns." Hardware revisions change rarely; firmware changes every release. Separate files let you reissue the SBOM without touching the HBOM, as long as the HBOM's link points at the right SBOM version.

Step 9: declare dependencies and compositions

Nesting shows physical containment. The dependencies array makes the relationships machine readable for graph tools. The schema asks that components with no dependencies "must be declared as empty elements within the graph," which is why the example lists leaf parts such as dram with only a ref.

[
  {"ref": "eg200", "dependsOn": ["eg200-mainboard", "bootloader", "os-image"]},
  {"ref": "eg200-mainboard", "dependsOn": ["soc", "dram", "emmc", "tpm", "wifi-module", "eth-phy"]},
  {"ref": "os-image", "dependsOn": ["bootloader"]},
  {"ref": "soc"},
  {"ref": "dram"}
]

Then state how complete your knowledge is. compositions takes an aggregate value such as complete, incomplete, unknown or one of the first party and third party variants. In the example we declare the gateway and its main board complete, because we listed every code carrying part on them, and the Wi-Fi module and TPM unknown, because we buy them as sealed parts and cannot see what else is inside. Saying "unknown" out loud is far more useful to a recipient than an inventory that silently looks finished.

Step 10: validate the HBOM

Run the official CLI against the file:

./cyclonedx validate --input-file eg200.hbom.cdx.json \
  --input-format json --input-version v1_7 --fail-on-errors
echo "exit code: $?"

On our complete example the CLI printed Validating JSON BOM... then BOM validated successfully. and exited with code 0.

To see what a failure looks like, we misspelled one field (descripton instead of description) and one property name (cdx:device:lotNumbr). The CLI exited with code 1 and reported Some additional properties did not match the required schema: ["descripton"], pointing at the component's schema path. It said nothing about lotNumbr. That is expected: the schema checks the structure of a property, not its name. Check property names yourself, for example with a short script that compares every cdx: name in the file against the published taxonomy.

If you cannot run the CLI, validate with Python against the same schema files. Download bom-1.7.schema.json and the three files it references (spdx.schema.json, jsf-0.82.schema.json and cryptography-defs.schema.json) from the schema folder of the CycloneDX specification repository on GitHub, install jsonschema in a virtual environment, and run this script with python3 -I, which keeps the interpreter from loading modules from the current directory:

import json, sys
from jsonschema import Draft7Validator
from referencing import Registry, Resource

schema_dir, bom_path = sys.argv[1], sys.argv[2]
names = ["bom-1.7.schema.json", "spdx.schema.json",
         "jsf-0.82.schema.json", "cryptography-defs.schema.json"]
resources = []
for name in names:
    with open(f"{schema_dir}/{name}") as f:
        doc = json.load(f)
    res = Resource.from_contents(doc)
    resources += [(doc["$id"], res), (name, res)]
registry = Registry().with_resources(resources)

with open(f"{schema_dir}/bom-1.7.schema.json") as f:
    validator = Draft7Validator(json.load(f), registry=registry)
with open(bom_path) as f:
    errors = list(validator.iter_errors(json.load(f)))
for e in errors:
    print("/".join(map(str, e.absolute_path)), e.message)
print(f"{len(errors)} error(s)")
sys.exit(1 if errors else 0)
python3 -m venv .venv && .venv/bin/pip install jsonschema
.venv/bin/python -I validate_cdx.py ./schema eg200.hbom.cdx.json

With jsonschema 4.25.1 the script reported 0 errors on the example and 1 error on the misspelled copy ("Additional properties are not allowed ('descripton' was unexpected)"). Like the CLI, it did not flag the misspelled property name.

Step 11: sign the HBOM

The CLI has keygen, sign and verify commands. Here is what happened when we tried them on the JSON HBOM:

CommandResult in our test
cyclonedx sign bom eg200.hbom.cdx.jsonRefused: "Only XML BOMs are currently supported for signing." Exit code 3
cyclonedx verify all on a JSON BOMRefused: "Only XML BOMs are supported for signature verification."
cyclonedx sign file eg200.hbom.cdx.jsonWrote eg200.hbom.cdx.json.sig, a PKCS1 RSA SHA-256 signature
cyclonedx verify file on the untouched file"Signature successfully verified", exit code 0
cyclonedx verify file after changing one version string"Signature failed verification", exit code 6
openssl dgst -sha256 -verify with the same key and signature"Verified OK"

We also tried the XML route: convert to XML, then sign. The converted XML from v0.33.1 did not validate against the 1.7 XML schema, because the converter wrote an organization's url before its address, and the XSD expects address first. Until that is fixed, the detached signature on the JSON file is the simpler path.

# One time: create a key pair (keep the private key out of the repository)
./cyclonedx keygen --private-key-file hbom-signing.key --public-key-file hbom-signing.pub

# Sign the exact file you publish
./cyclonedx sign file eg200.hbom.cdx.json --key-file hbom-signing.key

# Recipient side: either tool works
./cyclonedx verify file eg200.hbom.cdx.json --key-file hbom-signing.pub
openssl dgst -sha256 -verify hbom-signing.pub \
  -signature eg200.hbom.cdx.json.sig eg200.hbom.cdx.json

A detached signature covers the exact bytes, so reformatting the JSON breaks it. Sign last, after validation, and publish the .sig and your public key alongside the file. For production use, keep the private key in a hardware security module or a cloud key service rather than a file on a build machine. The schema also defines an enveloped signature field in JSON Signature Format (JSF) at the BOM, component and composition level, but the CLI we tested does not produce it for JSON.

Step 12: publish and version it

Issue a new serialNumber for a new hardware revision, and increment version when you correct the same BOM. Serve it as application/vnd.cyclonedx+json, the media type the specification overview lists, and use a file name such as eg200-revC.hbom.cdx.json so it is not confused with the SBOM.

The complete example

This is the full HBOM we validated: 1.7 schema, CLI exit code 0, Python validator 0 errors. Values are invented for illustration.

{
  "$schema": "http://cyclonedx.org/schema/bom-1.7.schema.json",
  "bomFormat": "CycloneDX",
  "specVersion": "1.7",
  "serialNumber": "urn:uuid:6a6f5133-73ce-41c4-b881-829f3f9ea5fd",
  "version": 1,
  "metadata": {
    "timestamp": "2026-10-23T09:00:00Z",
    "lifecycles": [{"phase": "post-build"}],
    "authors": [{"name": "Hardware Compliance Team", "email": "hbom@example.com"}],
    "manufacturer": {
      "name": "Example Devices Inc.",
      "address": {"country": "US"},
      "url": ["https://example.com"]
    },
    "component": {
      "type": "device",
      "bom-ref": "eg200",
      "manufacturer": {
        "name": "Example Devices Inc.",
        "address": {"country": "US"},
        "url": ["https://example.com"]
      },
      "supplier": {
        "name": "Example Devices Inc.",
        "address": {"country": "US"},
        "url": ["https://example.com"]
      },
      "name": "EG-200 Edge Gateway",
      "version": "hw-rev-C",
      "description": "Fanless industrial edge gateway with Ethernet, Wi-Fi and Bluetooth",
      "properties": [
        {"name": "cdx:device:sku", "value": "EG200-C-US"},
        {"name": "cdx:device:certifications:US:FCC:id", "value": "EXAMPLE-EG200C"},
        {"name": "exampledevices:hbom:finalAssemblyLocCode", "value": "MX-JAL"},
        {
          "name": "exampledevices:hbom:finalAssemblyBy",
          "value": "Example Contract Manufacturing S.A."
        }
      ],
      "externalReferences": [
        {"type": "documentation", "url": "https://example.com/eg200/datasheet.pdf"},
        {"type": "rfc-9116", "url": "https://example.com/.well-known/security.txt"}
      ]
    },
    "distributionConstraints": {"tlp": "AMBER"}
  },
  "components": [
    {
      "type": "device",
      "bom-ref": "eg200-mainboard",
      "name": "EG200-MB",
      "version": "rev-3",
      "description": "Main board assembly (PCBA)",
      "manufacturer": {"name": "Example Contract Manufacturing S.A.", "address": {"country": "MX"}},
      "properties": [
        {"name": "cdx:device:function", "value": "main board"},
        {"name": "cdx:device:quantity", "value": "1"}
      ],
      "components": [
        {
          "type": "device",
          "bom-ref": "soc",
          "name": "EXS-A55Q-1G4",
          "version": "B1",
          "description": "Quad core Arm Cortex-A55 application processor",
          "supplier": {"name": "Example Components Distribution LLC", "address": {"country": "US"}},
          "manufacturer": {"name": "Example Silicon Ltd.", "address": {"country": "TW"}},
          "properties": [
            {"name": "cdx:device:function", "value": "microprocessor"},
            {"name": "cdx:device:location", "value": "U1"},
            {"name": "cdx:device:deviceType", "value": "SMD"},
            {"name": "cdx:device:quantity", "value": "1"},
            {"name": "cdx:device:lotNumber", "value": "L2614A07"},
            {"name": "exampledevices:hbom:compMfgLocCode", "value": "TW-HSZ"},
            {"name": "exampledevices:hbom:assyAndTestLocCode", "value": "MY-10"},
            {"name": "exampledevices:hbom:technologyNode", "value": "12 nm"}
          ],
          "externalReferences": [{"type": "documentation", "url": "https://example.com/parts/exs-a55q/datasheet.pdf"}]
        },
        {
          "type": "device",
          "bom-ref": "dram",
          "name": "EXM-LP4-2G-x32",
          "version": "A",
          "description": "2 GB LPDDR4 SDRAM",
          "manufacturer": {"name": "Example Memory Co.", "address": {"country": "KR"}},
          "properties": [
            {"name": "cdx:device:function", "value": "memory"},
            {"name": "cdx:device:location", "value": "U2"},
            {"name": "cdx:device:deviceType", "value": "SMD"},
            {"name": "cdx:device:quantity", "value": "1"},
            {"name": "cdx:device:lotNumber", "value": "M2631K"}
          ]
        },
        {
          "type": "device",
          "bom-ref": "emmc",
          "name": "EXM-EMMC51-16G",
          "version": "A",
          "description": "16 GB eMMC 5.1 storage",
          "manufacturer": {"name": "Example Memory Co.", "address": {"country": "KR"}},
          "properties": [
            {"name": "cdx:device:function", "value": "storage"},
            {"name": "cdx:device:location", "value": "U3"},
            {"name": "cdx:device:deviceType", "value": "SMD"},
            {"name": "cdx:device:quantity", "value": "1"}
          ]
        },
        {
          "type": "device",
          "bom-ref": "tpm",
          "name": "EXT-TPM20-SPI",
          "version": "7.85",
          "description": "TPM 2.0 security chip on SPI",
          "manufacturer": {"name": "Example Secure Chips GmbH", "address": {"country": "DE"}},
          "properties": [
            {"name": "cdx:device:function", "value": "security"},
            {"name": "cdx:device:location", "value": "U7"},
            {"name": "cdx:device:deviceType", "value": "SMD"},
            {"name": "cdx:device:quantity", "value": "1"}
          ],
          "components": [
            {
              "type": "firmware",
              "bom-ref": "tpm-fw",
              "name": "EXT-TPM20 firmware",
              "version": "7.85.4555.0",
              "supplier": {"name": "Example Secure Chips GmbH", "address": {"country": "DE"}},
              "hashes": [
                {
                  "alg": "SHA-256",
                  "content": "a2e672bc1bfd18a609322b3c7098cc3466859d8f2ac29d2204000844803e2f15"
                }
              ]
            }
          ]
        },
        {
          "type": "device",
          "bom-ref": "wifi-module",
          "name": "EXW-6E-M2",
          "version": "2.1",
          "description": "Wi-Fi 6E and Bluetooth 5.3 M.2 module",
          "manufacturer": {"name": "Example Wireless Corp.", "address": {"country": "TW"}},
          "properties": [
            {"name": "cdx:device:function", "value": "Wi-Fi and Bluetooth"},
            {"name": "cdx:device:location", "value": "J4"},
            {"name": "cdx:device:deviceType", "value": "module"},
            {"name": "cdx:device:quantity", "value": "1"},
            {"name": "cdx:device:certifications:US:FCC:id", "value": "EXAMPLE-EXW6E"},
            {"name": "cdx:device:macAddress", "value": "02:00:5E:10:00:01"}
          ],
          "components": [
            {
              "type": "firmware",
              "bom-ref": "wifi-fw",
              "name": "exw6e-firmware",
              "version": "83.2.1",
              "supplier": {"name": "Example Wireless Corp.", "address": {"country": "TW"}},
              "hashes": [
                {
                  "alg": "SHA-256",
                  "content": "eb11cdba7f122321568d8dab856012fdfdee207676bea0ea9aa47d6818c98836"
                }
              ]
            }
          ]
        },
        {
          "type": "device",
          "bom-ref": "eth-phy",
          "name": "EXP-GPHY-1G",
          "version": "C",
          "description": "Gigabit Ethernet PHY",
          "manufacturer": {"name": "Example Silicon Ltd.", "address": {"country": "TW"}},
          "properties": [
            {"name": "cdx:device:function", "value": "network"},
            {"name": "cdx:device:location", "value": "U9"},
            {"name": "cdx:device:deviceType", "value": "SMD"},
            {"name": "cdx:device:quantity", "value": "2"}
          ]
        }
      ]
    },
    {
      "type": "firmware",
      "bom-ref": "bootloader",
      "name": "u-boot",
      "version": "2025.04-eg200.3",
      "description": "Bootloader stored in eMMC boot partition",
      "supplier": {"name": "Example Devices Inc.", "address": {"country": "US"}},
      "hashes": [
        {
          "alg": "SHA-256",
          "content": "a963a804e932a2c2270ecae29086e22018e2f15c939f5dfffa1d96b9542aa871"
        }
      ],
      "externalReferences": [
        {
          "type": "bom",
          "url": "urn:cdx:66409f65-4644-4aca-9289-844f92fc2168/1#bootloader",
          "comment": "Bootloader SBOM in the EG-200 firmware SBOM"
        }
      ]
    },
    {
      "type": "operating-system",
      "bom-ref": "os-image",
      "name": "eg200-os",
      "version": "4.2.1",
      "description": "Embedded Linux image (kernel, root file system, applications)",
      "supplier": {"name": "Example Devices Inc.", "address": {"country": "US"}},
      "hashes": [
        {
          "alg": "SHA-256",
          "content": "219f28bbc427583841f83833f97b68a75fed18ce79454d280ca578e3067aab86"
        }
      ],
      "externalReferences": [
        {
          "type": "bom",
          "url": "urn:cdx:66409f65-4644-4aca-9289-844f92fc2168/1",
          "comment": "Full software SBOM for firmware release 4.2.1"
        },
        {
          "type": "bom",
          "url": "https://example.com/eg200/sbom/eg200-os-4.2.1.cdx.json",
          "hashes": [
            {
              "alg": "SHA-256",
              "content": "b52b57c18fc9eea8dd59d3c9044805b46880f6cd596a20b25748ee5dc9463c35"
            }
          ]
        },
        {"type": "release-notes", "url": "https://example.com/eg200/firmware/4.2.1/notes"}
      ]
    }
  ],
  "dependencies": [
    {"ref": "eg200", "dependsOn": ["eg200-mainboard", "bootloader", "os-image"]},
    {
      "ref": "eg200-mainboard",
      "dependsOn": ["soc", "dram", "emmc", "tpm", "wifi-module", "eth-phy"]
    },
    {"ref": "os-image", "dependsOn": ["bootloader"]},
    {"ref": "tpm", "dependsOn": ["tpm-fw"]},
    {"ref": "wifi-module", "dependsOn": ["wifi-fw"]},
    {"ref": "soc"},
    {"ref": "dram"},
    {"ref": "emmc"},
    {"ref": "eth-phy"},
    {"ref": "bootloader"},
    {"ref": "tpm-fw"},
    {"ref": "wifi-fw"}
  ],
  "compositions": [
    {"aggregate": "complete", "assemblies": ["eg200", "eg200-mainboard"]},
    {"aggregate": "unknown", "assemblies": ["wifi-module", "tpm"]}
  ]
}

The SPDX 3 alternative, and its limits

SPDX 3.0.1, released in December 2024, has no hardware element type. What it has is a software purpose vocabulary that includes device ("a chipset, processor, or electronic board") and firmware ("provides low level control over a device's hardware"), plus deviceDriver. You can describe hardware parts as software_Package elements with software_primaryPurpose set to device, and link them with contains relationships. We built this minimal SPDX 3.0.1 JSON-LD version of the gateway and validated it against the official SPDX 3.0.1 JSON schema with zero errors. A copy with the purpose changed to hardware failed, because that value is not in the vocabulary.

{
  "@context": "https://spdx.org/rdf/3.0.1/spdx-context.jsonld",
  "@graph": [
    {
      "type": "CreationInfo",
      "@id": "_:creationinfo",
      "specVersion": "3.0.1",
      "created": "2026-10-23T09:00:00Z",
      "createdBy": ["https://example.com/spdx/org/example-devices"]
    },
    {
      "type": "Organization",
      "spdxId": "https://example.com/spdx/org/example-devices",
      "creationInfo": "_:creationinfo",
      "name": "Example Devices Inc."
    },
    {
      "type": "Organization",
      "spdxId": "https://example.com/spdx/org/example-silicon",
      "creationInfo": "_:creationinfo",
      "name": "Example Silicon Ltd."
    },
    {
      "type": "software_Package",
      "spdxId": "https://example.com/spdx/eg200/device",
      "creationInfo": "_:creationinfo",
      "name": "EG-200 Edge Gateway",
      "software_packageVersion": "hw-rev-C",
      "suppliedBy": "https://example.com/spdx/org/example-devices",
      "software_primaryPurpose": "device"
    },
    {
      "type": "software_Package",
      "spdxId": "https://example.com/spdx/eg200/soc",
      "creationInfo": "_:creationinfo",
      "name": "EXS-A55Q-1G4",
      "software_packageVersion": "B1",
      "originatedBy": ["https://example.com/spdx/org/example-silicon"],
      "software_primaryPurpose": "device",
      "comment": "Location U1, lot L2614A07, fab location TW-HSZ"
    },
    {
      "type": "software_Package",
      "spdxId": "https://example.com/spdx/eg200/os-image",
      "creationInfo": "_:creationinfo",
      "name": "eg200-os",
      "software_packageVersion": "4.2.1",
      "software_primaryPurpose": "firmware",
      "verifiedUsing": [
        {
          "type": "Hash",
          "algorithm": "sha256",
          "hashValue": "219f28bbc427583841f83833f97b68a75fed18ce79454d280ca578e3067aab86"
        }
      ]
    },
    {
      "type": "Relationship",
      "spdxId": "https://example.com/spdx/eg200/rel-1",
      "creationInfo": "_:creationinfo",
      "from": "https://example.com/spdx/eg200/device",
      "relationshipType": "contains",
      "to": [
        "https://example.com/spdx/eg200/soc",
        "https://example.com/spdx/eg200/os-image"
      ]
    },
    {
      "type": "SpdxDocument",
      "spdxId": "https://example.com/spdx/eg200/document",
      "creationInfo": "_:creationinfo",
      "name": "EG-200 hardware and firmware inventory",
      "rootElement": ["https://example.com/spdx/eg200/device"]
    }
  ]
}

The limits are real, and you should state them to anyone who receives an SPDX HBOM:

NeedCycloneDX 1.7SPDX 3.0.1
A hardware component typetype deviceOnly software_primaryPurpose device on a software package
Organization address or countryaddress.country on manufacturer and supplierNo address or country field in the schema
Reference designator, quantity, lotRegistered cdx:device propertiesFree text comment, or an extension you define
Firmware image hashhashesverifiedUsing with a Hash
Link to firmware SBOMexternalReferences type bom, BOM-LinkRelationships and external references between documents
Completeness statementcompositionscompleteness on relationships

A dedicated Hardware profile does exist in the SPDX 3 model repository, with classes such as PhysicalHardware, BulkHardware, VirtualHardware, ProductSpecification and Dimensions, described as metadata "related to physical and virtual hardware properties." It sits on the develop branch, not in the released 3.0.1 specification. SPDX published a 3.1 release candidate (v3.1-RC1) in January 2026; we could not confirm from the release notes whether the Hardware profile will ship in 3.1. Until a release includes it, an SPDX HBOM depends on comments and conventions that each recipient has to agree to. If your customers mandate SPDX for software, a pragmatic split is SPDX for SBOMs and CycloneDX for the HBOM, linked by document references.

Common mistakes

  • Putting firmware version in the device version. The hardware revision and the firmware release change on different schedules. Give firmware its own component, as the schema's device definition says.
  • Using metadata.manufacturer for the device maker. It means the organization that created the BOM. The device maker belongs on metadata.component.manufacturer.
  • Treating manufacturer.address.country as country of origin. It is the company's address. Fab, assembly and test locations need their own fields, as CISA's taxonomy separates them.
  • Inventing names under cdx:. The taxonomy forbids unofficial names there. Use your own namespace and document it.
  • Assuming validation checks property names. It does not. A typo in cdx:device:lotNumber passes schema validation.
  • Down-converting without removing 1.7 fields. distributionConstraints and isExternal fail 1.6 validation.
  • Listing a sealed module as if you knew its insides. Mark it unknown or incomplete in compositions.
  • Hashing nothing. Without firmware hashes, an HBOM cannot tell you whether the unit in front of you runs the firmware you think it does.
  • Relying on cyclonedx sign bom for JSON. In the version we tested it refuses JSON and exits with a non-zero code, so a CI step that ignores the exit code would ship an unsigned file.

Where Safeguard fits, honestly

Safeguard does not generate HBOMs, and we have not documented any HBOM specific handling of hardware components. The hardware half of this guide (the device tree, part numbers, lot codes and origin data) comes from your engineering and procurement records, built by hand or with your PLM tooling, and validated with the CycloneDX CLI as shown above.

Safeguard helps with the software half that an HBOM points to. The firmware SBOM you link in Step 8 is an ordinary CycloneDX or SPDX SBOM, and Safeguard's SBOM generation and analysis works with both formats. You can upload an SBOM, or scan one from the command line with safeguard scan --sbom eg200-os-4.2.1.cdx.json, match its components against vulnerability databases, and use reachability analysis to rank what your code actually calls. Safeguard also accepts CSAF and VEX uploads, so a supplier's exploitability statements can sit next to the findings. The Third-Party Risk Manager lets you request SBOMs from suppliers, which is how you get the firmware SBOM for a bought in module like the Wi-Fi card in our example.

What Safeguard does not do here: it does not read part numbers off a board, verify country of origin, detect counterfeit chips, scan firmware images for you, or manage devices in the field. Those remain jobs for your hardware, procurement and device management processes.

Fix it without waiting for upstream

A device's firmware is mostly open source software: a bootloader, a kernel and a root file system full of libraries, and the firmware SBOM your HBOM links to is where their vulnerabilities show up. Hardware fixes take a new board revision, but the open source components you build into firmware can be patched on your own schedule.

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 situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull 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.

FAQ

What is the difference between an HBOM and an SBOM in CycloneDX?

There is no format difference. Both are CycloneDX BOMs. An HBOM's main components have type device and describe physical parts; an SBOM's components are libraries, applications, containers and similar software. CycloneDX lets one document hold both, but most teams keep them separate and link them with an external reference of type bom.

Does CycloneDX have a dedicated HBOM schema?

No. The same bom-1.7.schema.json covers software, hardware, services, machine learning models and cryptographic assets. Hardware specific names live in the official property taxonomy under cdx:device.

Which CycloneDX version supports HBOMs?

Both 1.6 and 1.7 have the device type and manufacturer addresses. Use 1.7, the current version, unless a recipient's tooling only reads 1.6.

Where do I put the manufacturer part number in CycloneDX?

In the component name. CISA's HBOM framework maps its COMP_MANUFACTURER_PN field to the CycloneDX name. Put the revision or stepping in version and the maker in manufacturer.

How do I record country of origin in a CycloneDX HBOM?

There is no country of origin field and no registered property for it. manufacturer.address.country records the organization's address only. For fab, assembly and test locations, add properties in a namespace you control, use ISO 3166 codes as CISA's taxonomy does, and tell the recipient what each property means.

How do I validate a CycloneDX HBOM?

Run cyclonedx validate --input-file FILE --input-format json --input-version v1_7 --fail-on-errors with the official CLI and check that the exit code is 0. Alternatively, validate with a JSON Schema library against bom-1.7.schema.json and the three schema files it references. Neither check catches misspelled property names.

Can the CycloneDX CLI sign a JSON HBOM?

Not with sign bom in v0.33.1: it supports XML BOMs only and refuses JSON. Use cyclonedx sign file to produce a detached RSA SHA-256 signature, and verify it with cyclonedx verify file or openssl dgst -sha256 -verify.

Should firmware go in the HBOM or the SBOM?

Both, at different depths. The HBOM lists each firmware image as a component with its version and hash, so you know what runs on which part. The SBOM lists what is inside that firmware. Link the two with a bom external reference.

Can I make an HBOM in SPDX instead?

Partly. SPDX 3.0.1 can label a package with the device or firmware purpose, but it has no hardware element, no address or country field and no registered hardware properties. A Hardware profile is in development on the SPDX 3 model's develop branch and is not part of a released specification yet.

See how Safeguard would help

None of these demos build an HBOM. They show the software side, which is where the firmware SBOM linked from your HBOM ends up. To try the CLI, download it from https://safeguard.sh/download and read the setup guide at https://docs.safeguard.sh/docs/cli .

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. The same approve each step flow applies to the open source code in a firmware build: find the vulnerable dependency, fix it locally, rebuild, and issue a new firmware SBOM for your HBOM to link.

Click through it at your own pace, or open it full screen.

The second demo sets up the Safeguard runner on your own machine, approves it with its device code, and then scans from the terminal. Firmware teams often build on isolated machines; this shows how scanning can run locally where the firmware build and its SBOM already live.

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. If you use an assistant to review the software BOMs your HBOMs point to, this is how you give it access to findings without granting more than you intend.

Click through it at your own pace, or open it full screen.

Quick check

Your HBOM passes cyclonedx validate, but a recipient says the lot numbers are missing. How can both be true?

Property names are not checked by the schema. If the file says cdx:device:lotNumbr instead of cdx:device:lotNumber, it validates, but a tool looking for the registered name finds nothing. Check property names against the official taxonomy separately.

Why does the example HBOM mark the Wi-Fi module and TPM as "unknown" in compositions?

They are bought as sealed parts, so the device maker cannot see everything inside them. Declaring unknown tells the recipient the inventory below that level is not complete, instead of letting a short list look finished.

Sources

Last reviewed: 23 October 2026. CycloneDX, SPDX and the CycloneDX CLI change between releases; check the linked schemas and release pages before relying on a specific behaviour.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.