An SBOM is a list of every component that went into building a piece of software — direct dependencies, transitive dependencies, their versions, where they came from, and often their licenses. That sounds like paperwork until the next Log4Shell happens and someone asks "are we affected?" The difference between answering that in ten minutes and answering it after three days of grepping through vendored jars is whether you generated an SBOM at build time.
What an SBOM actually contains
At minimum, a useful SBOM has one entry per component with a name, a version, a supplier or origin, and some kind of unique identifier — a package URL (purl), a CPE, or both. Good SBOMs also carry the dependency graph (this library pulled in that library), license identifiers, and cryptographic hashes of the actual artifact so you can verify it's the file you think it is, not a typosquatted or tampered substitute. What an SBOM is not: a vulnerability report. It's an inventory. Vulnerability data gets matched against that inventory afterward, usually by a separate tool that consumes the SBOM and cross-references it against a vulnerability database like the NVD or OSV.
This distinction matters because it's what makes SBOMs durable. A vulnerability scan result is stale the day after a new CVE is published. An SBOM generated against a specific build is accurate forever, because it's just describing what's in that build. You regenerate the vulnerability match, not the inventory.
SPDX vs CycloneDX, and when each is preferred
Two formats dominate, and they came from different starting problems. SPDX (Software Package Data Exchange) started life at the Linux Foundation focused on license and provenance tracking for open source compliance — it's the older, more formally standardized of the two, and it's now an ISO/IEC standard (5962:2021). CycloneDX came out of the OWASP community with security use cases as the primary driver — vulnerability correlation, VEX (Vulnerability Exploitability eXchange) statements, and a lighter, more developer-friendly JSON shape.
In practice: if your primary driver is license compliance, legal review, or you're working with a toolchain that already speaks SPDX (many government and Linux distro pipelines do), reach for SPDX. If your primary driver is vulnerability management and you want to attach VEX statements saying "yes this CVE is in the tree but it's not reachable in our usage," CycloneDX has better first-class support for that. Neither is going away — most serious SBOM tooling (Syft, Trivy, cdxgen) can emit both, and plenty of organizations generate both formats from the same build and hand off whichever one a given consumer wants.
Generating an SBOM in CI
The only SBOM that's worth anything is one generated automatically, at build time, from the actual artifact that ships — not a manually maintained spreadsheet that drifts out of date the week after someone writes it. Anchore's Syft is the tool I reach for most: it points at a directory, a container image, or an SBOM-adjacent file like a lockfile, and walks it to produce a CycloneDX or SPDX document. cdxgen is a solid alternative, especially for JavaScript and polyglot monorepos.
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myorg/myservice:${{ github.sha }}
format: cyclonedx-json
output-file: sbom.cdx.json
- name: Upload SBOM as build artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.cdx.json
That produces a document shaped roughly like this — a component entry per dependency, with a purl and a version, nested under a metadata block describing the artifact it was generated for:
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"components": [
{
"type": "library",
"name": "log4j-core",
"version": "2.14.1",
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
"licenses": [{ "license": { "id": "Apache-2.0" } }]
}
]
}
Attach that file to the build as an artifact, push it to an internal SBOM store or registry alongside the image, and you now have a queryable record for every build you've ever shipped — not just the one currently in production.
Scanning source or a lockfile tells you what you intended to ship. Scanning the actual container image or binary tells you what you shipped — including base image packages, anything pulled in at build time, and anything a Dockerfile RUN step installed that never touched a manifest.
How SBOMs get used during incident response
This is the scenario that made SBOMs go from a compliance checkbox to something security teams actually push for. When Log4Shell (CVE-2021-44228) hit in December 2021, the honest answer at most companies to "which of our services use log4j-core, and which version?" took days, because nobody had an inventory — they had source repos, and grepping every repo and every vendored dependency across every service is slow and error-prone, especially for transitive dependencies three levels deep that nobody remembers pulling in.
With SBOMs already generated per build and stored somewhere queryable, that same question becomes a query: search every stored SBOM for a component matching pkg:maven/org.apache.logging.log4j/log4j-core with a version below the patched one. You get a list of affected services in minutes instead of days, ranked by exposure, without needing engineers to context-switch off whatever they were doing to go hunt. That's the entire pitch for doing this proactively — the value shows up exactly once you're already under pressure, which is also exactly the wrong time to start building the inventory from scratch.
A folder of JSON files scattered across build artifacts in CI is technically an SBOM archive, but nobody will find the right one under pressure. Feed generated SBOMs into a store or a simple database keyed by service and build, so "who runs log4j-core 2.14.1" is a query, not a scavenger hunt.
Regulatory and procurement drivers
SBOMs stopped being a niche security practice around 2021, when US Executive Order 14028 on cybersecurity directed NIST and NTIA to define minimum elements for an SBOM and pushed federal software vendors toward providing one. That order didn't mandate SBOMs for every company on earth, but it had the usual ripple effect: agencies started asking vendors for SBOMs as part of procurement, prime contractors started asking their subcontractors, and it's now common to see "can you provide an SBOM for this software" show up in vendor security questionnaires well outside government contracting. The EU's Cyber Resilience Act pushes in a similar direction for products with digital elements sold in the EU.
Separately from any government requirement, plenty of enterprise customers now ask for an SBOM as a standard part of security review before signing a contract, the same way they'd ask about SOC 2 or penetration test reports. If you're selling software to anyone who takes procurement seriously, having a current SBOM ready to hand over — rather than scrambling to generate one when asked — is worth doing regardless of whether a regulation technically requires it of you.
Wrapping up
The unglamorous truth about SBOMs is that most of the value comes from the boring part: generating one automatically on every build and putting it somewhere searchable. The format debate between SPDX and CycloneDX matters less than actually having current data when the next Log4Shell-shaped question lands on your desk. Start with Syft in CI producing CycloneDX JSON per image, store the output, and you've done the 80% that actually pays off.
Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.