SBOM: Software Bill of Materials and Why You Should Care About Your Dependencies
Knowing what is actually in your software before someone else tells you

Every time a serious supply-chain vulnerability lands, the same scramble begins. Someone in a chat channel asks “are we affected?” and the honest answer, for most teams, is “give us a few days and we’ll tell you.” That few days is the gap an SBOM is meant to close. A Software Bill of Materials is just an inventory — a machine-readable list of every component that went into a build — but having one ready before the panic is the difference between an afternoon and a fortnight.
1 What an SBOM actually is
Strip away the acronym and an SBOM is a manifest. For each thing you ship, it lists the packages, libraries, and transitive dependencies that made it in, ideally with versions, licences, and cryptographic hashes. Two formats dominate: SPDX (an established Linux Foundation standard, good for licence compliance) and CycloneDX (born in the security community, strong on vulnerability tooling). Both are interchangeable enough that most tools can convert between them, so don’t agonise over the choice — pick CycloneDX if security is your driver.
The crucial word is transitive. Your package.json lists maybe forty direct dependencies. The actual dependency graph is closer to nine hundred packages, and the thing that bites you is almost always five layers deep, pulled in by a library you’ve never heard of. An SBOM flattens that graph into something you can grep.
2 Generating one
You don’t write an SBOM by hand. You generate it from a build artefact. Syft (from Anchore) is the tool I reach for because it understands containers, filesystems, and most language ecosystems without configuration:
# Inspect a built image and emit CycloneDX JSON
syft myapp:1.4.0 -o cyclonedx-json > sbom.json
# Or scan a project directory directly
syft dir:. -o spdx-json > sbom.spdx.jsonThe output is verbose, but the shape is simple — an array of components, each with a purl (package URL) that uniquely identifies it:
{
"components": [
{
"type": "library",
"name": "log4j-core",
"version": "2.14.1",
"purl": "pkg:maven/org.apache.logging.log4j/[email protected]",
"licenses": [{ "license": { "id": "Apache-2.0" } }]
}
]
}That purl is the magic. It’s a standardised identifier that vulnerability databases key off, which means an SBOM and a scanner together can answer “are we affected?” in seconds.
3 Closing the loop with vulnerability scanning
An SBOM on its own is just a list. The value comes from feeding it to something that knows about CVEs. Grype pairs naturally with Syft and reads the SBOM directly, so you scan the inventory rather than re-scanning the artefact:
# Scan the SBOM we already generated
grype sbom:sbom.json --fail-on highNAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
log4j-core 2.14.1 2.17.1 java CVE-2021-44228 CriticalThe --fail-on high flag is what makes this useful in CI: the pipeline goes red when a high-or-worse vulnerability appears in something you actually ship. Generate the SBOM at build time, store it as an artefact alongside the image, and you’ve got a permanent record of what each release contained. When a new CVE drops months later, you scan the stored SBOMs — no need to rebuild ancient images.
4 Wiring it into CI
The pattern that has served me best: generate, store, scan, gate. Here’s the gist in a GitHub Actions step, though the same three commands work anywhere:
- name: Generate and scan SBOM
run: |
syft "ghcr.io/acme/api:${GITHUB_SHA}" \
-o cyclonedx-json > sbom.json
grype sbom:sbom.json --fail-on high
- name: Attach SBOM to release
uses: actions/upload-artifact@v4
with:
name: sbom-${{ github.sha }}
path: sbom.jsonIf you sign images with cosign, attach the SBOM as an attestation so it travels with the artefact and can be verified by whoever pulls it. That turns the SBOM from a file on a CI runner into a provable claim about your release.
5 The honest limitations
SBOMs are inventories, not lie detectors. They tell you what’s present, not whether it’s reachable or exploitable in your particular usage — a vulnerable function you never call still shows up as a finding, which generates noise. That’s what VEX (Vulnerability Exploitability eXchange) documents are meant to address, by recording “yes it’s there, no it’s not exploitable, here’s why,” but VEX tooling is still maturing and the discipline of maintaining it is real work. SBOMs also only capture what the generator can see; a binary blob vendored without metadata is invisible, and dynamically loaded plugins slip through.
6 Is it worth it?
For anyone shipping software to customers, regulated industries, or government — yes, and increasingly it’s non-negotiable as procurement requirements catch up. For a hobby project running on your own homelab, generating an SBOM is overkill you’ll never look at. The sweet spot is any team large enough that “are we affected?” can’t be answered from one person’s memory. Adding two commands to a pipeline you already have is close to free, and the first time a big CVE lands and you answer in five minutes instead of five days, it pays for itself. Start with Syft and Grype, gate on high severity, and ignore VEX until the noise actually bothers you.




