Safeguard
Supply Chain Security•

uSWID Explained: Embedding an SBOM in UEFI Firmware With CoSWID and LVFS

uSWID wraps CoSWID SBOM tags in a findable header so firmware carries its own SBOM. Learn the format, the LVFS flow, EDK2 and coreboot builds, and limits.

Safeguard Security Team
48 min read

uSWID is a small container format that lets a firmware image carry its own software bill of materials. It wraps one or more CoSWID tags (the compact CBOR form of ISO/IEC 19770-2 SWID tags, defined in RFC 9393) behind a 16 byte magic value, so a tool can find the SBOM inside a firmware blob it cannot otherwise parse. The LVFS extracts that data on upload and publishes SPDX, SWID and CycloneDX exports.

This guide goes one level deeper than our posts on SWID and CoSWID tags and on firmware updates with fwupd and the LVFS. We read the python-uswid README, source code and version history, the Firmware Embedded SBOM Specification published by the Open Source Firmware Foundation, the LVFS documentation, the fwupd source, the coreboot SBOM documentation, RFC 9393, NIST's SWID guidance, CISA's HBOM framework and 2025 SBOM minimum elements draft, the CycloneDX 1.6 and SPDX 3 specifications, and the European Commission's Cyber Resilience Act summary. We also installed uswid 0.6.0 in a clean virtual environment and ran every command in the walkthrough. Byte counts and hex dumps below come from that run.

Key takeaways

  • A normal SBOM tool walks a package manager's lockfile. Firmware has no lockfile: it is a flash image built by several companies from source modules, prebuilt binaries and vendor blobs, so the SBOM has to be produced at build time and shipped with the image.
  • CoSWID (RFC 9393, June 2023) is the CBOR encoding of SWID tags. It uses small integer keys instead of XML element names, which is why a single component tag fits in a few hundred bytes.
  • One CoSWID tag describes one piece of software. uSWID is a container that holds many tags, adds optional zlib or LZMA compression, and puts a 16 byte magic value in front so scanners can find it by searching the image.
  • Portable Executable (PE) binaries, which is what most UEFI drivers are, should carry CoSWID in a .sbom COFF section instead, with no uSWID header, because the PE header already says where the section is and the Authenticode signature covers it.
  • The LVFS scans every uploaded firmware for both forms, merges what it finds into a composite SBOM, and offers SPDX, SWID and CycloneDX downloads.
  • The open source uswid tool (BSD-2-Clause-Patent, on PyPI) creates, merges, validates and converts this data. It reads INI files, pkg-config files, EDK2 .inf files, PE binaries and raw images, and writes CoSWID, SWID XML, CycloneDX and SPDX.
  • coreboot builds can emit a sbom.uswid file into CBFS through Kconfig options under General setup.
  • The data is only as good as what vendors write. A binary blob with no metadata, or a component named only by a GUID with no package URL or CPE, cannot be matched against a vulnerability database automatically.

Why is firmware hard to describe with a normal SBOM?

Most SBOM generators work by reading a manifest. For a Node.js project they read package-lock.json; for a container image they read the distribution's package database. The manifest lists names, versions and often hashes, and each name maps to a package URL (PURL) that a vulnerability database understands. The SBOM is close to a reformatted lockfile.

UEFI system firmware has none of that. A typical image is a set of firmware volumes in SPI flash containing dozens of PE32+ drivers and PEI modules, CPU microcode, silicon initialization binaries, a management or security processor firmware region, an embedded controller image, option ROMs and a boot logo. The Firmware Embedded SBOM Specification describes the difficulty in terms of three parties: the firmware vendor that writes most of the source, the ODM that compiles it with extra code and binaries, and the OEM that adds its own extensions and ships the result. Each one sees only part of the picture.

Several concrete problems follow from that structure.

No package manager, no lockfile

EDK2 (the TianoCore open source UEFI implementation) builds each module from an .inf file that names its sources and the library classes it links. That file tells you which C files went in, but not which upstream release of a shared library a vendor fork started from. There is no central registry like npm or PyPI that resolves BaseCryptLib to a version. A scanner that only looks at the finished image sees a few hundred files inside firmware volumes, each named by a GUID.

Prebuilt binaries arrive from other companies

Silicon vendors ship memory and silicon init code as binaries. CPU microcode is a binary. The LVFS documentation names "AMD microcode or Intel FSP" as examples of blobs where embedding metadata directly is not possible because the format is proprietary. The firmware vendor links these in without source, so the only way to describe them accurately is for the supplier to describe them.

Version strings do not follow any common scheme

Firmware modules often carry a version in a build header or a string table, but there is no shared format. One module says 1.0, another carries a build date, another a hexadecimal revision. CISA's 2023 HBOM framework uses "Firmware Version 13.3.3.2" as an example value, which illustrates the point: a human can read it, but nothing ties it to a known list of vulnerable releases.

The image changes hands after it is built

An OEM can rebuild a capsule, replace a logo, or add a module long after the firmware vendor's build. The specification argues that a detached SBOM file is easy to forget at that point: "If the SBOM is part of the image itself and automatically constructed as part of the deliverable, then it is impossible to forget." Embedding is the answer it proposes.

Space is tight

SPI flash is small and every byte has a hardware cost. That rules out large text formats. RFC 9393's abstract says it directly: XML SWID tags "can be too large for devices with network and storage constraints." That constraint is why the firmware world settled on CBOR.

What is uSWID, and how does it relate to CoSWID and SWID tags?

Three formats stack on top of each other here, and it helps to separate them.

SWID tags (ISO/IEC 19770-2)

Software Identification tags come from ISO/IEC 19770-2; RFC 9393 builds on the 2015 edition, ISO/IEC 19770-2:2015. A SWID tag is an XML document that identifies one software product, patch or installation bundle: a unique tag ID, a name, a version, the entities that created, licensed or distributed it, optional file payload details and links to related tags. NIST published NISTIR 8060, "Guidelines for the Creation of Interoperable Software Identification (SWID) Tags", in April 2016 to make tags from different producers consistent. NIST's SWID project page describes the original use case: SWID tags "provide a transparent way for organizations to track the software installed on their managed devices." The ISO text itself sits behind a paywall, which is one reason the coreboot documentation calls SWID "a standard hidden behind an ISO paywall."

CoSWID (RFC 9393)

RFC 9393, "Concise Software Identification Tags", is an IETF Standards Track document published in June 2023. Its authors came from Fraunhofer SIT, the US National Security Agency, MITRE and NIST. CoSWID keeps the SWID data model but encodes it in CBOR, the Concise Binary Object Representation, and replaces every element name with a small integer. The RFC's CDDL lists them: tag-id = 0, software-name = 1, entity = 2, evidence = 3, link = 4, software-meta = 5, payload = 6, hash = 7 and so on up to unspsc-version = 57. Entity roles are integers too (tag-creator = 1, software-creator = 2, aggregator = 3, distributor = 4, licensor = 5, maintainer = 6), as are link relations such as component = 2, requires = 8 and see-also = 9.

A few registry facts from the RFC are useful when you are looking at bytes:

  • The media type is application/swid+cbor, the file extension is .coswid, and the CoAP Content-Format ID is 258.
  • IANA allocated CBOR tag 1398229316 for a CoSWID map. If a CoSWID is wrapped in that tag, the first five bytes in hex are da 53 57 49 44, which spells SWID after the tag marker.
  • A CoSWID can be signed by wrapping it in a COSE_Sign1 or COSE_Sign structure.
  • An entity with the tag-creator role is mandatory in every tag; software-creator is recommended.

RFC 9393 keeps the four SWID tag types: primary (the installed product), patch, corpus (the installer or package before installation) and supplemental (extra metadata layered onto another tag without changing it).

uSWID

uSWID is not a standards body document. The coreboot documentation is blunt about it: "uSWID is not a standard or specification but it doesn't need to be, since it's basically just an array/list of CoSWID" tags. It was created in the python-uswid project by Richard Hughes, the LVFS maintainer, with the first release on PyPI in October 2021. It solves two practical problems CoSWID leaves open:

  1. One CoSWID tag describes one piece of software. A firmware image contains many. uSWID holds a sequence of tags in one payload.
  2. A CoSWID blob has no marker. A tool that cannot parse the surrounding vendor format does not know where the CBOR starts. uSWID adds a fixed 16 byte magic value that a scanner can search for anywhere in an image.

The rules for when to use each wrapper now live in the Firmware Embedded SBOM Specification, revision 0.10, dated 21 February 2024, which the LVFS documentation says it moved to the Open Source Firmware Foundation. It requires CoSWID with CBOR encoding for embedded SBOMs, "due to the small compiled size of data compared to SPDX (YAML or JSON) and SWID (XML)".

Timeline

DateEventSource
2015ISO/IEC 19770-2:2015 edition of SWID tags, the version RFC 9393 builds onRFC 9393
April 2016NIST publishes NISTIR 8060 on interoperable SWID tagsNIST CSRC
17 May 2021Executive Order 14028 published in the Federal RegisterFederal Register
12 October 2021First uswid releases (0.0.1 and 0.1.0) on PyPIPyPI
June 2023RFC 9393 (CoSWID) published as an IETF Standards Track RFCRFC Editor
25 September 2023CISA publishes its HBOM frameworkCISA
3 December 2023uswid 0.4.7 adds LZMA compression and --validateuswid version history
21 February 2024Firmware Embedded SBOM Specification revision 0.10, text imported from the LVFSOSFF specification
9 May 2024uswid 0.5.0 adds initial VEX support and relicenses to BSD-2-Clause-Patentuswid version history, PyPI
10 December 2024EU Cyber Resilience Act enters into forceEuropean Commission
August 2025CISA publishes the 2025 SBOM minimum elements draft for commentCISA
16 March 2026uswid 0.6.0 on PyPI, allowing CycloneDX or SPDX inside a uSWID containerPyPI, uswid version history
11 September 2026CRA reporting obligations under Article 14 applyEuropean Commission
11 December 2027CRA main provisions applyEuropean Commission

How the three compare

PropertySWID (ISO/IEC 19770-2)CoSWID (RFC 9393)uSWID container
EncodingXMLCBOR with integer keysBinary header plus a payload of CoSWID tags (or, from header v4, CycloneDX or SPDX)
Components per documentOneOneMany
Access to the specificationPaid ISO standardFree IETF RFCOpen source README and the OSFF specification
Self locating inside a blobNoOnly if wrapped in CBOR tag 1398229316Yes, 16 byte magic
CompressionNone definedNone definedOptional zlib or LZMA
SigningXML digital signatureCOSE_Sign1 or COSE_SignCovered by the firmware image or capsule signature if included in it
Typical size for one componentKilobytesA few hundred bytesA few hundred bytes compressed
Where you see it in firmwareRarely embedded; used for export.sbom section of PE binariesRaw images, firmware volumes, appended regions, coreboot CBFS

How is a uSWID blob embedded in a firmware image, and how is it found later?

The header

The python-uswid README and the fwupd source both define the header. Every uSWID blob starts with this 16 byte magic value:

53 42 4F 4D D6 BA 2E AC A3 E6 7A 52 AA EE 3B AF

The first four bytes are ASCII SBOM. fwupd's struct definition writes the same bytes as the GUID 4d4f4253-bad6-ac2e-a3e6-7a52aaee3baf, which is the identical value in mixed endian GUID notation. After the magic come the header fields, all little endian:

OffsetSizeFieldNotes
016magicSBOM plus 12 fixed bytes
161header version1 to 4
172header lengthOffset from the start of the header to the payload
194payload lengthBytes of payload after the header
231flags0x01 means the payload is compressed (v2 and later)
241compression type0x00 none, 0x01 zlib, 0x02 LZMA (v3 and later)
251payload format0x00 CoSWID, 0x01 CycloneDX, 0x02 SPDX (v4 only)

The header grew one byte per version, and that explains a small inconsistency you will see in the documents. The README calls it "the 25 byte uSWID header" in one place and mentions "a 24 byte header" in another, while the field list it prints adds up to 26 bytes. The source code settles it: python-uswid writes a version 2 header of 24 bytes for uncompressed or zlib CoSWID, a version 3 header of 25 bytes when LZMA is used, and a version 4 header of 26 bytes when the payload is CycloneDX or SPDX. The OSFF specification (written before v4) documents the 25 byte v3 header and adds that the header length "MAY be increased for alignment reasons (e.g. to 0x100 bytes)", padded with NUL bytes. Readers should always trust the header length field, not a fixed size.

What the bytes look like

We built a one component SBOM and saved it with zlib compression. The first 32 bytes:

00000000: 5342 4f4d d6ba 2eac a3e6 7a52 aaee 3baf  SBOM......zR..;.
00000010: 0218 00df 0000 0001 789c 5bc9 9f9a 9aa7  ........x.[.....

Reading it: magic, then 02 (header version 2), 18 00 (header length 24), df 00 00 00 (payload length 223), 01 (compressed). The payload starts at byte 24 with 78 9c, the usual zlib stream header. The whole file is 247 bytes: 24 of header plus 223 of payload.

Saving the same data with --compression lzma --format cyclonedx produced a version 4 header instead:

00000000: 5342 4f4d d6ba 2eac a3e6 7a52 aaee 3baf  SBOM......zR..;.
00000010: 041a 00cc 0200 0001 0201 fd37 7a58 5a00  ...........7zXZ.

Header version 4, header length 26 (0x1a), payload length 716 (0x2cc), compressed, LZMA (02), CycloneDX (01). The payload begins with the xz stream magic fd 37 7a 58 5a 00. The file is 742 bytes, three times the CoSWID version, because a JSON CycloneDX document is far wordier than CBOR even after compression. That is the size argument for CoSWID in one comparison.

Where the blob goes

The OSFF specification and the LVFS documentation describe three placements.

Inside a PE binary, in a .sbom section. Most UEFI drivers are PE32+ files. For these, the specification says component vendors should include CoSWID "in a .sbom COFF section which can be easily included at link time", and that these binaries "MUST NOT use the magic header of uSWID" because the PE header already gives the section's offset and size. A side benefit the specification points out: the section is covered by the existing Authenticode signature on the binary. You can check a binary with objdump -s -j .sbom payload.efi, which the uswid README recommends.

Inside a non PE component, with a uSWID header. For microcode, silicon init binaries, embedded controller images and anything else that is not a PE file, the vendor "MUST use the discoverable uSWID header so that software can easily discover the embedded SBOM." The LVFS documentation lists where such a blob might sit: "in an FV section for an EFI image, the DT for an ARM image, or just appended as raw data in a free section in the ROM file." The README adds FIT images, where the SBOM section holds a uSWID blob.

As a defragmented firmware SBOM. A firmware vendor can place one top level uSWID blob in the image that already merges every component SBOM. If every component already carries its own metadata, this blob is optional. If not, the vendor must include it, and it must contain every component SBOM present, so tools do not have to deduplicate.

How a tool finds it

Discovery is deliberately simple. python-uswid's loader calls blob.find() for the magic bytes, parses the header at each hit, decompresses the payload and decodes the CoSWID tags one after another, then keeps searching from the end of that payload. fwupd's FuUswidFirmware parser in libfwupdplugin reads the same structure, including all three compression types and all three payload formats. For PE files, both tools read the .sbom section from the section table instead. If several uSWID sections are present, the LVFS documentation says they are appended into one result.

The specification also covers runtime access. If the platform allows reading the SPI flash, you can dump the whole image to a file and run uswid on it. It mentions a future ACPI SBOM table for returning CoSWID from a running system, with details to be provided once implemented, so do not plan around it yet.

Detached metadata, when embedding is not possible

The specification allows a separate SBOM file in two cases: when the binary goes into a critically space constrained place (it names microcode loaded into the processor), and when older releases of a component must stay compatible. In that case the vendor must ship the metadata from the same source as the blob and must include the blob's SHA-256 hash as evidence, so anyone can check the SBOM matches the file.

How does the LVFS use uSWID data, and what does it show?

The Linux Vendor Firmware Service is the service OEMs upload firmware to so that fwupd can deliver it to Linux machines. Its introduction page says millions of customer devices are updated every month through it. Our fwupd and LVFS post covers the update flow; here is what happens to SBOM data.

Extraction on upload

The LVFS claims documentation says: "All firmware uploaded to the LVFS gets scanned for both CoSWID data embedded in the SBOM section of the COFF binaries, but also uSWID external metadata." It gives an example of CoSWID found in 75 PE files, "where each EFI binary contributes information to the composite package SBOM." When multiple uSWID sections are detected, they are appended.

What users see

The OSFF specification describes the result: when firmware is uploaded, the LVFS "automatically extracts all available SBOM metadata and generates a HTML page with SPDX, SWID and CycloneDX download links that can be used for compliance purposes." In practice that means a person evaluating a laptop or a peripheral can look at the firmware release on the LVFS and download an SBOM in a format their tools already read, without asking the OEM for a file.

The LVFS also uses SBOM presence as one of the "claims" it can show for a firmware release, alongside items such as whether source is available or whether the device is end of life. A claim tells a buyer what the vendor provided; it is not an audit of the contents.

The web generator

For vendors who do not want to install anything, the LVFS hosts an SBOM generation helper at fwupd.org/lvfs/uswid. Its form asks for a tag ID (usually a GUID), software name, version, product, summary, colloquial version, edition, an entity name and registration ID with roles, and an optional SPDX license link. It exports a uSWID header plus CoSWID (marked recommended), plain CoSWID, SWID XML or the INI source.

VEX and the "trusted neutral entity"

The specification argues that only the party with the source tree and build configuration can say whether a given EFI binary is affected by a vulnerability, and that asking each vendor by hand does not scale. It recommends that component vendors and platform vendors upload SBOMs to a trusted neutral entity, which "MAY also be the LVFS, even for firmware updates not distributed by the LVFS." That entity can then apply VEX statements from component vendors to mark firmware as affected or not affected. VEX product IDs must use the same identifier as the SBOM, written as a PURL with the GUID as the name, for example pkg:dca533ab-2c1f-4327-9b2b-09ac19533404@15.35.2039. The specification itself says further details on VEX "will be provided in the future", so treat this part as direction rather than a finished process.

The uswid tool already reads OpenVEX and CSAF 2.0 documents and prints a report. The README's example loads a microcode SBOM and a VEX file, and shows a statement marking a 2022 CVE as NOT_AFFECTED with the justification VULNERABLE_CODE_NOT_IN_EXECUTE_PATH.

What metadata should each component carry?

The OSFF specification sets out required and recommended fields. Mapping them to CoSWID and to the INI keys uswid uses makes the rules easier to apply.

Requirement in the specificationStrengthCoSWID fielduswid INI key
Identifier in the form of a GUIDMUSTtag-id (0)tag-id
Non empty descriptive name, no file extensionMUSTsoftware-name (1)software-name
At least one entity; one with tag-creator, one with software-creatorMUSTentity (2) with role (33)[uSWID-Entity:Role] sections, extra-roles
Entity registration ID is the DNS name of the organisation or projectMUSTreg-id (32)regid
Version, preferably semanticMUST, SHOULD be semversoftware-version (13), version-scheme (14)software-version, version-scheme
Revision if patched downstream, as {release}.{dist}SHOULDrevision (54)revision
Hash over all source files (SHA-1 or SHA-256)MUST for binaries built from sourcecolloquial-version (45)colloquial-version
Source tree hash, such as from gitSHOULDedition (47)edition
License links to SPDX license URLs for open sourceMUST for open sourcelink with rel license[uSWID-Link] with rel = license
Compiler and linker as linked componentsSHOULDlink with see-also[uSWID-Link:gcc] with rel = compiler
Statically linked libraries that VEX may referenceSHOULDlink with requiresrel = requires
Where to download the componentMAYlink with installationmediarel = installationmedia

Some details are easy to miss:

  • Identifier choice. If the component already has a GUID from a UEFI specification or reference implementation, use it. For UEFI firmware, the README says the tag ID "is typically the ESRT GUID value." For things like a compiler with no GUID, the specification says to derive one with a swid: prefix; the uSWID UEFI example uses a UUID version 5 of the name, so gcc becomes f43cae5a-baea-5023-bc90-3a83cd4785cc. Forked components "MUST have an identifier different from the upstream component identifier", and the identifier should not include the version.
  • Redaction. Where a relationship cannot be disclosed yet for contractual reasons, the specification allows the literal text REDACTED, but any component containing it must fail validation. uswid 0.5.0 added a validation failure for exactly that.
  • Business relationships. The specification warns vendors not to make business relationships public "that are not already in the public domain" through entity roles. That is a practical reason some SBOMs name fewer entities than you might expect.
  • Overhead. The specification estimates about 350 bytes for a single component with one entity, compressed with zlib. For a worst case platform SBOM of 1,000 plausible components, uswid's generator produces about 140 kB uncompressed or about 60 kB with LZMA, which the specification puts at 1.1 percent of the average free space it measured in a BIOS region.

How do you generate uSWID data with python-uswid, step by step?

The steps below ran on macOS with Python 3 and uswid 0.6.0 from PyPI (released 16 March 2026 according to PyPI). Linux is identical. The tool depends on cbor2, lxml and pefile.

Step 1: install the tool in a virtual environment

python3 -m venv ~/venvs/uswid
~/venvs/uswid/bin/pip install uswid==0.6.0
~/venvs/uswid/bin/uswid --help | head -5

Pin the version in CI so a new release does not change your output without review. The README also documents pip install --user uswid and installing from the git main branch for in development code.

Step 2: describe a component in INI

INI is the easiest format to write by hand and to keep in version control next to the module source. Here is a minimal component for a DXE driver:

[uSWID]
tag-id = 2119bbd7-9432-4f47-b5e2-5c4ea31b6bdc
software-name = ExampleDxe
software-version = 1.2.3
version-scheme = multipartnumeric
product = ExampleDxe
summary = Example DXE driver for a demo board
edition = 3090e61ee3452c0478860747de057c0269bfb7b6

[uSWID-Entity:TagCreator]
name = Example Firmware Ltd
regid = example.com
extra-roles = SoftwareCreator

[uSWID-Link:license]
rel = license
href = https://spdx.org/licenses/BSD-2-Clause-Patent.html

For a detached SBOM, add a payload section so the SBOM can be checked against the file. uswid fills in name, size and SHA-256 if you give a path:

[uSWID-Payload]
path = ../../build/ExampleDxe.efi

An empty [uSWID-Evidence] section makes uswid add the build date and machine name, which supports the "Timestamp" and "Generation Context" ideas in CISA's minimum elements work.

Step 3: build a compressed uSWID blob

uswid --load uswid.ini --save example.uswid --compression zlib
ls -l example.uswid
xxd example.uswid | head -2

Our run produced a 247 byte file with the version 2 header shown earlier. Saving with a .uswid extension is what adds the header. The same input saved as example.coswid produced 237 bytes of bare CBOR with no header, because there was no compression and no container.

Step 4: let downstream parties append their own entity

A firmware vendor builds the module; an ODM or OEM distributes it. The README shows how the next party adds itself without touching the original file:

# oem.ini
[uSWID-Entity:Distributor]
name = OEM Vendor
regid = oem.homepage.com
uswid --load example.uswid --load oem.ini --save example.uswid --compression zlib

uswid increments the tag version when it saves a tag it loaded from an existing binary. Set an explicit tag-version in the [uSWID] section if you need a fixed value.

Step 5: add links for compilers and bundled libraries

[uSWID-Link:gcc]
rel = compiler
href = swid:077b4576-92f7-52fd-94eb-af9fc3d52c58

[uSWID-Link:libpng]
rel = requires
href = swid:077b4576-92f7-52fd-94eb-af9fc3d52c58

Each swid: reference must point to a component that exists in the same SBOM, so load a compiler.ini or libpng.ini with the matching tag ID in the same command. If a component was defined earlier in the run, href = swid:libjpeg resolves by name.

Step 6: embed it, then find it again

To test discovery, we placed the blob in the middle of a dummy image:

head -c 8192 /dev/zero > image.bin
cat example.uswid >> image.bin
head -c 2048 /dev/zero >> image.bin
uswid --load image.bin --verbose

Output (trimmed):

Found USWID header at offset: 8192
Loaded:
uSwidComponent(tag_id="2119bbd7-9432-4f47-b5e2-5c4ea31b6bdc",tag_version="0",software_name="ExampleDxe",software_version="1.2.3"):
 - uSwidLink(rel="license",href="https://spdx.org/licenses/BSD-2-Clause-Patent.html")
 - uSwidEntity(regid="example.com",name="Example Firmware Ltd",roles=[TAG_CREATOR,SOFTWARE_CREATOR])

uswid treats .uswid, .raw and .bin files as images to search for the magic. For a PE file, it writes CoSWID into the .sbom section instead:

uswid --load sbom.ini --save ./payload.efi --objcopy /usr/bin/objcopy
objdump -s -j .sbom payload.efi

The README says writing modified PE files with the default pefile library "has only been lightly tested", which is why it offers the objcopy route. In a real build, the better path is to add the section at link time.

Step 7: validate

uswid --load image.bin --validate

Our minimal component passed with no output. The OSFF specification shows what failures look like on a real image: missing software name, a registration ID written as a URL instead of a DNS name, no entity marked as tag creator, a payload with no SHA-256 hash, and missing license or compiler links. Run validation in CI and fail the build on any problem.

Step 8: convert to CycloneDX and SPDX

uswid chooses the output format from the file name. Names ending bom.json or cdx.json are CycloneDX, names ending spdx.json are SPDX:

uswid --load image.bin --save bom.cdx.json --save sbom.spdx.json

From our run, the CycloneDX file declared specVersion 1.6 and this component:

{
  "type": "firmware",
  "name": "ExampleDxe",
  "version": "1.2.3",
  "description": "Example DXE driver for a demo board",
  "bom-ref": "2119bbd7-9432-4f47-b5e2-5c4ea31b6bdc",
  "licenses": [{ "license": {
    "url": "https://spdx.org/licenses/BSD-2-Clause-Patent.html",
    "id": "BSD-2-Clause-Patent" } }],
  "supplier": { "name": "Example Firmware Ltd", "url": ["example.com"] }
}

We trimmed the externalReferences and properties arrays. Note what is missing: no purl and no cpe. The component type firmware is a real CycloneDX value, defined in the 1.6 schema as "a special type of software that provides low-level control over a device's hardware." SPDX 3 has the equivalent firmware value in its software purpose vocabulary. The SPDX file uswid wrote was SPDX 2.3, with the package name, version, summary, originator and declared license.

The OSFF specification recommends that OEMs publish the SPDX or CycloneDX export on the public device web page and use the SHA-256 checksum of the generated SBOM as its collection ID, so it can be found even if product URLs change.

Step 9: collect component SBOMs across a tree

uswid --find ~/src/edk2 --save platform.uswid --compression lzma

--find walks a directory and picks up SBOM files by fixed names: bom.coswid, sbom.cdx.json, sbom.ini, sbom.spdx.json and swid.xml. It is a quick way to assemble a platform SBOM from per module files. --fixup (added in 0.5.1) repairs components with missing version control data, and the text formats accept substitutions such as @VCS_TAG@ and @VCS_COMMIT@ so release tooling does not need to hardcode versions.

Using uswid as a Python library

The documentation on Read the Docs shows the API. Loading components from a uSWID container is three lines:

from uswid import uSwidFormatUswid

with open("image.bin", "rb") as f:
    for component in uSwidFormatUswid().load(f.read()):
        print(component)

How do EDK2 and coreboot builds add component metadata?

EDK2

EDK2 modules already describe themselves in .inf files. The uSWID UEFI example repository shows the relevant parts:

[Defines]
  BASE_NAME       = CryptoDxe
  FILE_GUID       = 2119BBD7-9432-4f47-B5E2-5C4EA31B6BDC
  VERSION_STRING  = 1.0
[Sources]
  CryptoDxe.c
  CryptoDxe.h
[LibraryClasses]
  BaseMemoryLib
  HobLib

uswid can read .inf files directly through its EDK2 INF importer. Reading the source, it takes the component name from BASE_NAME (and refuses the file if that is missing), the version from VERSION_STRING (falling back to NOASSERTION), computes the colloquial version as a SHA-256 over every file listed under [Sources], collects SPDX-License-Identifier lines from those sources as licenses, adds each entry in [LibraryClasses] as a component link under pkg:github/tianocore/edk2, stores FILE_GUID as the persistent ID, and adds authors from git history. When a parent EDK2 component is defined in an sbom.cdx.json file, the importer copies its PURL with the module as a subpath and builds a CPE of the form cpe:2.3:a:tianocore:edk2:<version>:....

The UEFI example uses the meson build system to show a per module flow: each module turns its .inf into an .ini, merges a per module supplemental.ini (which adds the entities, the part an .inf cannot tell you) and a per project toplevel.ini, and writes a per module .uswid. After all modules build, the temporary files are squashed together with any static data from silicon vendors. The example's final demo.uswid, with five components including a DXE, gcc, a microcode blob, an EFI application and a modem baseband, is 842 bytes.

The OSFF specification says EDK2 builds should add metadata to each PE binary's .sbom section at compile time, put it in the defragmented firmware SBOM, or both, and that if this is not automatic, the firmware vendor still must make sure it is present. Commercial firmware build systems vary, so the specification avoids prescribing a single method.

coreboot

coreboot generates its SBOM as part of the build. The coreboot documentation describes the pipeline: the build gathers SBOM data for each component, and the goswid tool "links them all together into one sbom.uswid file", which ends up inside CBFS, coreboot's file system in flash. Our coreboot post covers CBFS and payloads in more depth.

The Kconfig options sit under a "Software Bill of Materials (SBOM)" submenu directly under General setup:

  • CONFIG_INCLUDE_[software-name]_SBOM includes or excludes a component's SBOM.
  • CONFIG_SBOM_[software-name]_PATH points at a SWID or CoSWID file in JSON, XML or CBOR.
  • CONFIG_SBOM_[software-name]_GENERATE, where available, generates basic data by analysing a binary or querying git. The documentation says this is done for all payloads, which get the commit hash used in the build.

The defaults are generic CoSWID JSON files in src/sbom/. The documentation is firm that "using the defaults is by any means to be avoided, since they hold very little information or even worse wrong information." For binary blobs, it asks the blob vendor to generate a SWID, CoSWID or uSWID file during their own build and ship it alongside the blob, preferably not inside it, and for the integrator to point the matching _PATH option at it.

coreboot also documents an unsolved question. If a blob such as an embedded controller or BMC image already carries its own uSWID data, should coreboot copy it into the platform SBOM? Copying duplicates data; skipping it means the coreboot SBOM is incomplete. The documentation prefers leaving out the SBOM of components that "can function on its own", because they may be updated without updating coreboot, but notes the implementation does not handle this yet.

Option ROMs and other add in firmware

Expansion card option ROMs are often UEFI drivers in PE format, so the .sbom section approach applies to them in principle. In practice, card vendors ship them as opaque images, and the OSFF specification lists OptionROMs among components that should carry SBOM metadata. Our PCI option ROM post explains how they load and how Secure Boot checks them.

What do CISA's HBOM work and the EU CRA mean for firmware SBOMs?

Executive Order 14028 and the US starting point

Executive Order 14028, "Improving the Nation's Cybersecurity", was published in the Federal Register on 17 May 2021 and put SBOMs on the US federal procurement agenda. The OSFF specification cites it, together with the US Cyber Trust Mark program, as the reason firmware SBOMs moved from nice to have to expected, and recommends that OEMs publish SPDX or CycloneDX exports to meet it. Check the current status of federal guidance before relying on any specific obligation, since agency guidance under the order has been revised several times.

CISA's HBOM framework covers firmware only lightly

CISA published "A Hardware Bill of Materials Framework for Supply Chain Risk Management" on 25 September 2023. It defines a taxonomy of fields for describing hardware components and maps them to CycloneDX and SPDX where possible. For firmware, the framework is explicit that SBOM information is out of scope. It "provides basic information about including the firmware associated with the products' components (i.e., the provider of the firmware), but stops short of proposing a framework for examining the provenance and other attributes of that firmware." In other words, an HBOM tells you a board has a particular controller and who supplies its firmware; an embedded firmware SBOM is what tells you what is inside that firmware. Our posts on the CISA HBOM framework and the HBOM complete guide go further.

CISA's 2025 SBOM minimum elements draft

In August 2025, CISA published a public comment draft, "2025 Minimum Elements for a Software Bill of Materials", building on the 2021 NTIA minimum elements. It adds four new fields: Component Hash, License, Tool Name and Generation Context. Generation Context records whether the SBOM was produced before build, during build or after build. It is a draft and says it does not represent final policy, but it lines up closely with what the firmware specification asks for:

CISA 2025 draft fieldFirmware SBOM equivalent
SBOM Author, Software ProducerEntities with tag-creator and software-creator roles
Component Name, Component Versionsoftware-name, software-version
Software Identifierstag-id GUID; PURL or CPE where one exists
Component Hash (new)Payload SHA-256; colloquial version over sources
License (new)License links to SPDX URLs
Dependency Relationshiprequires, see-also, component links
Tool Name (new)Not a native CoSWID field; record in the export
TimestampEvidence date
Generation Context (new)Embedded at build time means "during build"

The draft names SPDX and CycloneDX as the two widely used formats. That is why the LVFS and uswid convert CoSWID to those formats for publication: CoSWID is the embedding format, not the exchange format most buyers ask for.

The EU Cyber Resilience Act

The Cyber Resilience Act (Regulation (EU) 2024/2847) entered into force on 10 December 2024. According to the European Commission's summary, reporting obligations under Article 14 apply from 11 September 2026 and the main provisions from 11 December 2027. Annex I, Part II, which sets vulnerability handling requirements for manufacturers, asks them to identify and document components, including by drawing up a software bill of materials in a commonly used, machine readable format covering at least the product's top level dependencies. The SBOM forms part of the technical documentation and does not have to be published.

Firmware is software in a product with digital elements, so a device maker selling into the EU needs a component record for it. The CRA does not name a format. An embedded CoSWID SBOM gives the manufacturer a build time record that survives the supply chain, and an SPDX or CycloneDX export gives the documentation a format reviewers expect. The Act allows the Commission to specify SBOM format and elements later through implementing acts, so check for those before you settle on a final layout.

What are the limits?

uSWID makes it possible for firmware to describe itself. It does not make firmware describe itself correctly. These are the gaps to plan for.

Binary blobs without metadata

If a silicon vendor ships a blob with no SBOM and no detached file, the firmware vendor can describe it only from the outside: a name, a hash and a vendor. The LVFS documentation expects the integrator to "generate some external metadata about the non-free blob and include it in the system image somehow". That metadata is better than nothing, but it cannot list libraries compiled into the blob.

Identifiers that do not map to PURL or CPE

Vulnerability databases key on package URLs and CPE names. A firmware component identified only by a GUID and a vendor version like 15.35.2039 has neither unless someone adds one. Our CycloneDX export above had no purl field at all. The PURL specification does define a swid type (for example pkg:swid/Acme/example.com/Enterprise%2BServer@1.0.0?tag_id=...), and the OSFF specification proposes GUID based PURLs for VEX, but public vulnerability records rarely use either for firmware. The EDK2 importer's generated CPE helps for upstream EDK2 modules; it does not help for a vendor's own module or a fork, which the specification says must have a different identifier from upstream.

Versions that are not semantic

The specification says versions "SHOULD be a semantic version". RFC 9393 supports several version schemes (multipartnumeric, multipartnumeric with suffix, alphanumeric, decimal and semver) and explains that they partially overlap and sort differently. A tool comparing a dated build string to a vulnerable range cannot know which is newer unless the scheme is declared and consistent.

Trust in the metadata

The specification makes signing optional, with an honest reason: "a malicious silicon provider can typically do much worse things (e.g. adding or replacing a DXE binary) than modify the SBOM metadata." Inside a signed capsule or a signed PE binary, the SBOM inherits that signature. A uSWID blob appended to a raw ROM region with no signature over it is just bytes. Treat the SBOM as a statement by the vendor, not proof of contents. To check it, compare payload hashes against the binaries actually present.

Incomplete platform coverage

A laptop contains firmware for the system, the embedded controller, the touchpad, a card reader, the storage controller, docking chips and more. The specification notes that many of these "might not have any communication channel to the system firmware at all". A system firmware SBOM covers the system firmware. The platform SBOM is a union of many files, some of which may not exist.

Specification maturity

The OSFF specification is at revision 0.10, labelled an initial prerelease. The ACPI SBOM table and the VEX flow are described as future work. Expect fields and rules to change.

Common mistakes

  • Using a URL as the registration ID. The specification requires a DNS name such as example.com. uswid's validator flags http://www.example.com as invalid.
  • Adding a uSWID header inside a PE .sbom section. PE binaries must use bare CoSWID in the section. The header is for formats that cannot say where their SBOM is.
  • Shipping coreboot's default SBOM files. The coreboot documentation warns they carry little or wrong information. Point each CONFIG_SBOM_..._PATH at real data.
  • Generating the SBOM after the capsule is built, by hand. That is the out of sync problem the specification set out to remove. Make SBOM generation a build step and fail the build if validation fails.
  • Reusing the upstream GUID for a forked module. A forked component must have its own identifier, or a VEX statement about upstream will be wrongly applied to your fork, or the other way round.
  • Leaving out the compiler and statically linked libraries. These are what vulnerability advisories name. A DXE driver entry with no link to the crypto or image library it embeds will not match an advisory for that library.
  • Treating the CycloneDX export as complete. Converted components often lack PURL and CPE. Review the export and add identifiers where you have them before handing it to a customer or a regulator.
  • Assuming a fixed header size. Header versions 2, 3 and 4 differ in length, and the specification allows padding. Always read the header length field.
  • Publishing business relationships by accident. Entity roles reveal who built and distributed what. Review entity lists before the SBOM goes public.
  • Dumping or reflashing SPI flash casually to read the SBOM. Reading is low risk, but writing a modified image back is not. If you only need the SBOM, read the image or use the vendor's published export; never write to flash to inspect metadata.

FAQ

What does uSWID stand for?

The project does not expand the name in its README. It builds on SWID (Software Identification) tags and their concise CBOR form, CoSWID. In practice, uSWID refers to the container format with a 16 byte magic header that holds CoSWID tags, and to the python-uswid tool that reads and writes it.

Is uSWID an official standard?

No. CoSWID is an IETF standard (RFC 9393) and SWID is an ISO/IEC standard (19770-2). uSWID is an open source container defined by the python-uswid project. Its use in firmware is described by the Open Source Firmware Foundation's Firmware Embedded SBOM Specification, revision 0.10, which is a prerelease document.

What is the difference between SWID and CoSWID?

They share a data model. SWID tags are XML documents; CoSWID tags encode the same information in CBOR with integer keys, which makes them much smaller and suitable for embedding. RFC 9393 also adds some semantics SWID lacks, such as more ways to describe evidence and links.

How big is an embedded firmware SBOM?

Small. In our test, one component with an entity and a license link took 237 bytes as bare CoSWID and 247 bytes as a zlib compressed uSWID file. The OSFF specification estimates about 350 bytes per component with one entity, and about 60 kB for a worst case 1,000 component platform SBOM compressed with LZMA.

How do I check whether my laptop's firmware has an SBOM?

If the firmware is distributed on the LVFS, look at the release there for SBOM downloads. If you have the update file or a firmware image from the vendor, run uswid --load <file> --verbose; it searches for the uSWID magic and reads PE .sbom sections. On Linux, fwupd's parser library includes a uSWID firmware type, and fwupdtool get-firmware-types lists the parsers your installed version supports.

Can I convert a uSWID SBOM to CycloneDX or SPDX?

Yes. uswid --load firmware.bin --save bom.cdx.json --save sbom.spdx.json writes both. uswid 0.6.0 writes CycloneDX 1.6 with component type firmware, and SPDX 2.3.

Does the LVFS require an SBOM to upload firmware?

The LVFS documentation describes scanning every upload for CoSWID and uSWID data and showing SBOM availability as a claim on the release. It does not say an SBOM is mandatory for upload. Check the current LVFS vendor policies if you are a vendor.

Can I use uSWID SBOM data for vulnerability scanning?

Partly. Components that carry a PURL or CPE, or that name a well known open source library with a version, can be matched by tools that read CycloneDX or SPDX. Components identified only by a vendor GUID and a non standard version cannot be matched until someone publishes advisories or VEX using the same identifiers.

Does the CRA require firmware SBOMs?

The CRA requires manufacturers of products with digital elements to draw up an SBOM covering at least top level dependencies as part of vulnerability handling, with main provisions applying from 11 December 2027. Firmware is software in the product, so it falls in scope. The Act does not prescribe a format.

Should the SBOM be signed?

The specification makes it optional but says that if the firmware component is signed, the SBOM should be included in the signature. For PE binaries, the .sbom section is covered by the Authenticode signature automatically. For raw images, include the uSWID blob in whatever region the capsule or image signature covers.

Where Safeguard fits

Safeguard does not scan firmware images, read flash chips, extract embedded SBOMs or generate HBOMs, and it does not validate CoSWID or uSWID data. The fit is with the software around a firmware program. SBOM generation with uswid runs in a Python environment with its own dependencies; EDK2 and coreboot builds run in containers with toolchains and helper scripts; release and signing pipelines and any update service you operate are ordinary code repositories. If you export a CycloneDX or SPDX file from uswid, Safeguard's --sbom option reads SBOM files, but components named only by a vendor GUID with no PURL or CPE give any matcher, ours included, nothing to match against.

Embedded firmware SBOMs exist because firmware is full of open source code: EDK2 modules, crypto and image libraries, compilers, and the build and signing tooling around them all come from upstream projects that publish fixes. The fit below is indirect, since it covers the dependencies and container images you build and ship around firmware, not the vendor blobs or the image in flash.

Fix it without waiting for upstream

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.

See how Safeguard would help

The demos below show Safeguard on ordinary software projects, not on firmware images. Safeguard does not parse firmware, extract embedded SBOMs or generate HBOMs. It applies to the software around a firmware program: the Python environment that runs uswid, the build container, the signing and release scripts, and any update service you run. Safeguard can scan those with safeguard scan --dir ., --image for a container image, or --sbom for an SBOM file you already have, and it writes results as JSON under ./output.

The first demo is a real Safeguard CLI session. After install, safeguard opens the interactive terminal, you ask it to fix the lodash CVE in a repository, 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. It ends with /permissions, /model and /scan. The same ask, approve and test loop fits a firmware tooling repository, where you want every dependency change reviewed before it runs in the build.

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

Firmware build and signing machines are often kept off the general network. The local runner demo shows how to set up the runner on your own machine, approve it with its device code, and then scan from the terminal. A runner on a build host can scan the build tree and the containers that live there without sending source anywhere else.

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

If your team uses Claude, the third demo shows how to connect Claude to the Safeguard MCP server with OAuth and choose which tools Claude can use. An engineer can then ask about known vulnerabilities in the projects that hold your SBOM generation and release tooling, using only the tools you allowed.

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

To try it on your own repositories, download Safeguard and follow the CLI documentation.

Quick check

A UEFI driver vendor wants to embed CoSWID in its signed PE binary. Should it add the uSWID magic header?

No. The Firmware Embedded SBOM Specification says PE binaries should carry CoSWID in a .sbom COFF section and must not use the uSWID header, because the PE section table already gives the offset and length. The Authenticode signature then covers the SBOM too.

A scanner reads a uSWID header with version 3 and header length 0x100. Where does the payload start?

At 0x100 bytes from the start of the magic. The specification allows the header to be padded with NUL bytes for alignment, so a reader must use the header length field rather than assuming 24, 25 or 26 bytes.

Sources

Last reviewed: 23 October 2026. The Firmware Embedded SBOM Specification is a prerelease and uswid changes between releases; check the current specification and the version you install before building SBOM generation into a release pipeline.

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.