Skip to content
DevSecOps 12 min read

SBOMs for Teams Getting Asked for One: A Practical Guide

If a customer or auditor has asked you for an SBOM and you're not sure where to start, this is the short version. What an SBOM actually is, the two formats that matter, the toolchain we use, and the difference between an SBOM that ticks the box and one that does useful security work.

Why you're reading this

An enterprise customer just asked you for an SBOM as part of their security review. Or your auditor mentioned them in passing. Or your CISO read about Executive Order 14028 and now wants a "software bill of materials strategy." Whatever the reason, you have to produce one and you're not sure what it is, what format it should be in, or what the customer is going to do with it once you hand it over.

The good news is that the basic version of an SBOM is much simpler than the discourse suggests. The complicated version is what enterprises will eventually want from you, but you don't need to start there. This is the practical guide — what an SBOM actually is, the two formats that matter, the toolchain we use, and the difference between an SBOM that ticks the box and one that does useful security work.

Diagram

1. What an SBOM actually is

An SBOM is a list of every software component inside a thing you ship, with enough identifying information to look each component up in a database. That's it. The "thing" can be a binary, a container image, a firmware blob, an operating system, or an entire system. The "components" can be open-source libraries, commercial dependencies, system packages, or your own code.

The list typically includes, for each component: the name, the version, the supplier, a unique identifier (often a Package URL or PURL), a hash of the component file, and a relationship to other components (does it depend on this thing, does this thing contain it). Modern SBOMs also include licensing information, copyright statements, and increasingly, provenance metadata.

If you've ever generated package-lock.json, requirements.txt, or the output of dpkg -l, you've already produced something that's about 70% of the way to an SBOM. The remaining 30% is structure and metadata.

2. The two formats that matter

There are several SBOM formats. In practice, two cover almost everything: SPDX and CycloneDX. You should know what each one is for, and you should be able to produce both, because customers will ask for one or the other and the asks aren't predictable.

SPDX is the older format, originally focused on license compliance. It's maintained by the Linux Foundation and standardized as ISO/IEC 5962. SPDX has the deepest support in license-tracking tools and the most legal precedent. If your customer is asking for an SBOM for compliance reasons (especially open-source license review), they probably want SPDX.

CycloneDX is newer, originally from the OWASP project, and focused on security use cases. It has richer support for vulnerability data, services, and the kinds of metadata you'd want for supply-chain security. If your customer is asking for an SBOM as part of vulnerability management or supply-chain security, they probably want CycloneDX.

Both are JSON (and XML, and other serializations). Both are human-readable. Both are machine-parseable. Both can describe the same thing — they have different strengths but the core information overlaps. The good tools generate both from the same source.

3. The toolchain we use

You can build an SBOM by hand, but don't. The tooling is mature and free. The stack we deploy on engagements:

Syft (generation)

Syft is the de facto standard for generating SBOMs from container images, filesystems, and source repositories. One command produces an SBOM in either SPDX or CycloneDX format. It knows about every common package ecosystem (npm, pip, maven, gem, cargo, go modules, deb, rpm, alpine, and more) and can chain through container layers to find packages installed by any of them.

The typical invocation looks like syft myimage:tag -o cyclonedx-json > sbom.json. Wire that into your build pipeline as the last step before pushing the image. Now every build produces an SBOM as a build artifact.

Grype (vulnerability scanning)

Grype is the companion to Syft. It takes an SBOM (or an image) and matches the components against vulnerability databases (NVD, GitHub Security Advisories, Alpine secdb, and others). The output is a list of CVEs affecting your components, with severity, fix availability, and reachability where the data supports it.

The Syft+Grype combination gives you SBOM generation and vulnerability scanning from a single source of truth. The SBOM is the canonical list of what's in your image; Grype tells you which of those things have known issues.

cosign (signing)

An unsigned SBOM is meaningless to a customer with any security maturity. They have no way to verify it actually came from you. cosign (from Sigstore) lets you sign the SBOM with a keyless identity tied to your CI's OIDC token, producing a verifiable signature that customers can check.

The pattern is: build the image, generate the SBOM with Syft, sign the image with cosign, sign the SBOM as an attestation attached to the image. Customers pulling the image can verify both signatures with cosign before deploying.

Dependency-Track (storage and tracking)

If you're shipping more than a handful of services, you need somewhere to store SBOMs and track which versions are deployed where. OWASP Dependency-Track is the open-source option: upload SBOMs over an API, get a dashboard of components, vulnerabilities, and policy violations across your portfolio. Commercial alternatives exist (Anchore, JFrog Xray, Snyk). For most teams, Dependency-Track is the right starting point.

4. Generating an SBOM end to end

Here's the actual sequence for a containerized service. Total time, once: 30 minutes. Total time per build, after wiring it in: about 15 seconds.

  1. Install Syft and Grype on the CI runner. Both are single binaries, no dependencies.
  2. After the image build step, run syft <image> -o cyclonedx-json > sbom-cdx.json and syft <image> -o spdx-json > sbom-spdx.json. You now have both formats as build artifacts.
  3. Run grype sbom:./sbom-cdx.json -o json > vulns.json. You now have the vulnerability scan as a build artifact.
  4. Optionally, fail the build on critical CVEs in your application dependencies (use Grype's --fail-on critical flag with a configuration file that excludes the base image layers — see the container hardening post for the rationale).
  5. Sign the image and attach the SBOM as a Sigstore attestation: cosign attest --predicate sbom-cdx.json --type cyclonedx <image>.
  6. Upload the SBOM to Dependency-Track via its API for portfolio-wide visibility.

5. What an SBOM should actually contain

A barely-acceptable SBOM has component names and versions. A good SBOM also has:

  • Package URLs (PURLs). These are the canonical identifiers for components across ecosystems — pkg:npm/lodash@4.17.21, pkg:pypi/django@4.2.0, pkg:deb/debian/openssl@3.0.11. PURLs let downstream tooling resolve components to vulnerability data without ambiguity.
  • Hashes. SHA-256 of the component file. Lets a recipient verify they have the same component you described.
  • Supplier information. Who shipped the component? Helpful for attribution when something goes wrong upstream.
  • License information. SPDX license identifiers (MIT, Apache-2.0, LGPL-3.0-or-later). Required by some customers for license compliance review.
  • Dependency relationships. Does component A depend on B? Is A bundled inside C? This is what lets a vulnerability scanner answer "is the affected library actually reachable in this build?"

Syft produces all of these by default. If you're generating SBOMs by hand or with a homegrown script, you're probably missing several of them.

6. The nested-component problem

A container image has layers. Each layer has packages. Some of the packages are installed by the base image, some by your build, some bundled inside other packages (like a JAR file containing a vulnerable Log4j inside an application binary). A naive SBOM tool will catch the OS packages but miss the JAR, and you'll fail your customer's review when they find the Log4j your tool didn't.

This is where the difference between cheap and good SBOM tooling matters. Syft handles nested archives reasonably well, including JARs inside containers, Python wheels, and bundled binaries. It's not perfect — nothing is — but it catches the cases that matter. Test it on an image you know has nested components and verify the SBOM lists them.

7. VEX, the part that makes SBOMs actually useful

If your SBOM lists 200 components and 30 of them have known CVEs, your customer is going to come back with 30 questions. Most of those CVEs probably don't actually affect you — the vulnerable function isn't called, the attack vector isn't reachable, the dependency is a transitive that's never invoked. But your customer doesn't know that.

VEX (Vulnerability Exploitability eXchange) is a companion document that says, for each CVE: "we know about it, here's our assessment, here's why it does or doesn't affect us." The values are "affected," "not affected," "fixed," "under investigation," and "false positive," with a justification.

Producing VEX documents is the part of an SBOM program that produces the most value and the most pain. Value because it dramatically reduces the back-and-forth on customer security reviews. Pain because someone has to actually do the analysis. Start small — produce VEX statements for the top 10 noisiest CVEs against your shipped images. Add more as the program matures.

8. Storage and distribution

Where do SBOMs live? Three options, each suitable for different use cases.

  • Attached to the image as an attestation. Sigstore's cosign attest attaches a signed SBOM to the image in the registry. Customers who pull the image can pull the SBOM the same way. Good for "bundled with the artifact" distribution.
  • In a Dependency-Track instance. Customers with API access can query for SBOMs of specific versions. Good for ongoing relationships where the customer is integrated with your security program.
  • Self-service download. A page on your security site where customers can download the SBOM for any released version. Good for one-off requests from customers you don't have an ongoing integration with.

Most mature programs do all three. Pick one to start with — Sigstore attestations are the lowest effort if you're already signing images.

9. What customers actually do with your SBOM

The honest answer is: it depends on the customer's maturity. Less mature customers will put it in a folder and forget about it. Mid-maturity customers will scan it for known CVEs and ask you about anything critical. Mature customers will ingest it into their software supply-chain security platform, correlate it with their threat intelligence, and ask pointed questions about specific components.

Plan for the mature customer. Generate an SBOM that's actually correct, sign it, attach VEX statements where you can, and have a process for responding to component questions when they come in. The first time you do this it feels like a lot of work; by the third or fourth time, it's a routine deliverable that takes less time than the security questionnaire.

10. What not to do

A few patterns that get teams in trouble:

  • Don't generate SBOMs by hand. They will be wrong, they will be incomplete, and they will not survive contact with a real customer review. Use Syft.
  • Don't ship an SBOM without signing it. Unsigned SBOMs are meaningless to any customer with a security program. Sign with cosign.
  • Don't generate SBOMs once and call it done. Every build needs a fresh SBOM. The SBOM for last week's build doesn't describe this week's binary.
  • Don't treat SBOMs as just a compliance artifact. The same SBOM can drive your vulnerability management — same data, same source of truth, two outputs. Feed Grype from the SBOM you ship to customers and you get vulnerability scanning for free.
  • Don't pick a format you can't generate. If your customer asks for SPDX and your tooling produces CycloneDX, fix the tooling — both Syft and most modern tools generate both. Don't try to convert by hand.

The short version

An SBOM is a list of software components in a thing you ship, with enough metadata to look each one up in a database. Use Syft to generate it (in both SPDX and CycloneDX), Grype to scan it for vulnerabilities, cosign to sign it, and Dependency-Track to store and track it. Wire all of this into CI so every build produces a fresh SBOM as an artifact. For the noisy CVEs your scanner reports, produce VEX statements that explain whether you're actually affected. Distribute the SBOM as a Sigstore attestation attached to the image, via Dependency-Track, or as a self-service download. The first time, it's a week of plumbing. After that, it's a build step that runs in under a minute.

Customers asking for SBOMs aren't asking you to invent something — they're asking you to expose data you already have. The plumbing is what's missing for most teams. Once it's in, the deliverable becomes routine.

Need an SBOM program that works?

We integrate SBOM generation, signing, and vulnerability scanning into your pipeline so every build produces evidence — not just paperwork.