Safeguard
Supply Chain

SBOM Example: Reading a Real CycloneDX and SPDX Document

One component, two formats: a field-by-field walkthrough of a real CycloneDX and SPDX SBOM — purls, licenses, hashes, dependency graphs, and how to validate your own.

Safeguard Research Team
Research
6 min read

The fastest way to understand a software bill of materials is to read one. This SBOM example walks through the same application component in the two dominant formats — CycloneDX and SPDX — field by field: what identifies a component, why the purl is the field that matters most, where license and integrity data lives, and how dependency relationships are encoded. By the end, you should be able to open any SBOM your tooling produces and know exactly what a consumer can and cannot learn from it.

What does a real SBOM example look like in CycloneDX?

CycloneDX, maintained under OWASP, encodes an SBOM as JSON or XML with three parts you will read constantly: document metadata, a flat component list, and a dependency graph. Here is a trimmed but structurally complete document describing a payments API that depends on lodash:

{
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
  "version": 1,
  "metadata": {
    "timestamp": "2025-03-01T12:00:00Z",
    "tools": [{ "name": "syft", "version": "1.0.0" }],
    "component": {
      "type": "application",
      "name": "payments-api",
      "version": "4.2.0"
    }
  },
  "components": [
    {
      "type": "library",
      "bom-ref": "pkg:npm/lodash@4.17.21",
      "name": "lodash",
      "version": "4.17.21",
      "purl": "pkg:npm/lodash@4.17.21",
      "licenses": [{ "license": { "id": "MIT" } }],
      "hashes": [{ "alg": "SHA-256", "content": "6c7...e21" }]
    }
  ],
  "dependencies": [
    {
      "ref": "pkg:npm/lodash@4.17.21",
      "dependsOn": []
    }
  ]
}

A handful of fields do most of the work. bomFormat and specVersion tell a parser what schema to validate against. serialNumber gives this exact document a globally unique identity, so two SBOMs for two builds never collide. metadata.component names the thing the SBOM describes — the application itself, not one of its dependencies — and metadata.tools records what generated the document, which matters when you are judging how much to trust it.

Each entry in components describes one ingredient. The purl (package URL) is the load-bearing field: pkg:npm/lodash@4.17.21 encodes ecosystem, name, and version in one canonical string, and it is what vulnerability databases and SCA tools key on when matching advisories. The hashes array lets a consumer verify that the artifact referenced is byte-for-byte the one that was scanned. Finally, dependencies turns the flat list into a graph — without it, a consumer cannot tell a direct dependency from something five levels deep.

How does the same component look in SPDX?

SPDX predates CycloneDX, grew out of license compliance work at the Linux Foundation, and became an ISO standard (ISO/IEC 5962:2021). The same SBOM example in SPDX tag-value format looks like this:

SPDXVersion: SPDX-2.3
DataLicense: CC0-1.0
SPDXID: SPDXRef-DOCUMENT
DocumentName: payments-api-4.2.0
DocumentNamespace: https://example.com/spdxdocs/payments-api-4.2.0-3e671687
Creator: Tool: syft-1.0.0
Created: 2025-03-01T12:00:00Z

PackageName: lodash
SPDXID: SPDXRef-Package-npm-lodash
PackageVersion: 4.17.21
PackageDownloadLocation: https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz
PackageLicenseConcluded: MIT
PackageLicenseDeclared: MIT
ExternalRef: PACKAGE-MANAGER purl pkg:npm/lodash@4.17.21

Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-npm-lodash

Three things stand out. First, every element gets an SPDXID, and relationships between elements — DESCRIBES, DEPENDS_ON, CONTAINS — are declared explicitly as Relationship lines, which is how SPDX builds its graph. Second, SPDX distinguishes PackageLicenseDeclared (what the package claims about itself) from PackageLicenseConcluded (what the SBOM author determined after analysis) — a distinction CycloneDX does not enforce, and one that matters in license disputes. Third, the purl still appears, but as an ExternalRef rather than a first-class field, a reminder that SPDX's roots are in licensing rather than vulnerability matching.

Which SBOM fields actually matter to a consumer?

When a security team receives an SBOM from a vendor, five fields determine whether the document is useful or decorative:

  • Unique identifier (purl or CPE). Without a machine-resolvable identifier, components cannot be matched to advisories, and the SBOM cannot answer "are we affected?" during the next major disclosure.
  • Exact version. "lodash 4.x" is useless; 4.17.21 is actionable.
  • Dependency relationships. Knowing that a vulnerable package is a transitive dependency of a test tool, not the production application, changes the response entirely.
  • Hashes. They tie the document to a specific artifact, so a rebuilt or tampered binary is detectable.
  • Creation metadata. Tool, timestamp, and author tell you whether the SBOM reflects last week's release or a build from 2023.

These map closely to the NTIA "minimum elements" that US federal guidance established after Executive Order 14028: supplier, component name, version, unique identifiers, dependency relationships, author, and timestamp.

CycloneDX or SPDX: which should you produce?

Produce both if your tooling makes it cheap — most modern generators do. CycloneDX is the security-native choice: it models services and vulnerabilities, and it pairs with VEX so you can tell consumers which CVEs in the inventory are actually exploitable. SPDX carries the ISO standard designation and deeper license-expression support, which procurement and legal teams often ask for by name. Regulatory regimes, including the EU Cyber Resilience Act's SBOM expectations, generally accept either format, so the practical answer is to generate whichever your consumers request from the same build.

How do you generate and validate your own SBOM?

Generators like Syft, Trivy, and cdxgen produce both formats from a container image or source directory:

syft payments-api:4.2.0 -o cyclonedx-json > bom.json
syft payments-api:4.2.0 -o spdx-json > bom.spdx.json

Validate before you publish — schema-invalid SBOMs are common and quietly break downstream automation. The cyclonedx CLI validates CycloneDX documents, and pyspdxtools does the same for SPDX. Generate on every release build, attach the SBOM to the artifact, and version it alongside the release.

An SBOM is inert until something consumes it. Software composition analysis tools ingest the purl inventory and re-match it against new advisories continuously, which is what turns a static document into an early-warning system. If you are comparing how different platforms handle SBOM import and monitoring, our Snyk comparison covers that alongside scanning depth, and the Safeguard blog has companion guides on VEX and SBOM policy.

FAQ

What is the minimum a valid SBOM example must contain?

The NTIA minimum elements: supplier name, component name, component version, a unique identifier (purl or CPE), dependency relationships, the SBOM author, and a timestamp. An SBOM missing unique identifiers or relationships technically parses but fails at its main job — answering impact questions quickly.

Is an SBOM the same as a lockfile?

No. A lockfile is a build input for one ecosystem that pins what a package manager should install. An SBOM is a normalized, cross-ecosystem record of what actually shipped, including OS packages, vendored code, and binaries a lockfile never sees.

Do SBOMs list vulnerabilities?

Not by design. An SBOM is inventory; vulnerability status belongs in a companion document like VEX or CycloneDX's vulnerability schema. Separating them is deliberate — the inventory is stable per release, while vulnerability status changes daily.

How often should you regenerate an SBOM?

On every build that produces a distributable artifact. Regenerating on a calendar schedule instead of per-build guarantees drift between the document and the software it claims to describe.

Never miss an update

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