⏱️ Lectura: 14 min
In May 2021, the White House signed Executive Order 14028 following the SolarWinds supply chain attack. That order turned the SBOM (software bill of materials) into a legal requirement for every software vendor working with the US government. Seven months later, when CVE-2021-44228 (Log4Shell) exposed half the internet, that same requirement made the difference between responding in minutes or taking weeks.
📑 En este artículo
- TL;DR
- What an SBOM Is and Why It Matters
- How an SBOM Works Under the Hood
- Practical Examples
- Getting Started: Installing and Generating Your First SBOM
- Real-World Use Cases
- Common Mistakes and Best Practices
- Comparison with Alternatives
- Going Deeper
- Frequently Asked Questions
- Does an SBOM replace a vulnerability scanner?
- SPDX or CycloneDX, which should I choose if I don’t have an external requirement?
- Do you need to generate the SBOM on every build, or is once a month enough?
- Is an SBOM useful for your own code, or only for third-party dependencies?
- What happens if a dependency doesn’t have a recognizable purl?
- References
An SBOM solves a very concrete problem: it is an exact, machine-readable inventory of every component, version, and license that makes up a piece of software, generated automatically on every build. You’ll learn how to generate one with open source tools like Syft, how to scan it with Grype, and how to automate it in a CI/CD pipeline.
TL;DR
- You’ll understand what an SBOM is and why Executive Order 14028 made it mandatory in the US since 2021.
- You’ll learn to tell apart the SPDX and CycloneDX formats and know when to use each one.
- You’ll generate your first real SBOM with Syft in under a minute, no account or prior setup needed.
- You’ll scan that SBOM with Grype to detect known CVEs like Log4Shell before they affect you.
- You’ll automate SBOM generation and scanning in a GitHub Actions pipeline.
- You’ll distinguish a static build-time SBOM from a dynamic runtime one, and when you need each.
- You’ll learn the mistakes that make an SBOM useless the day a real incident hits.
What an SBOM Is and Why It Matters
An SBOM is, in essence, the ingredient list for your software. Just like a food label declares every component of a product, an SBOM declares every library, version, and license that goes into a binary, a container image, or a package. The difference from a package.json or a requirements.txt file is that the SBOM also includes transitive dependencies and is generated in a standard format that any tool can read.
The need isn’t theoretical. The SolarWinds attack in December 2020 compromised a software update that reached close to 18,000 organizations, including several US federal agencies. Most victims didn’t know what version of the affected component was running in their own infrastructure. A year later, Log4Shell repeated the problem at a different scale: a logging library so common that it was buried three or four levels below thousands of applications, and almost nobody had an inventory that explicitly mentioned it.
In 2026 the list of software supply chain incidents keeps growing: malicious packages on npm and PyPI, compromised scanning tools, credentials stolen through third-party dependencies. That recurring pattern is why regulators and security teams stopped treating the SBOM as a compliance exercise and started treating it as operational infrastructure: the inventory that answers in minutes the question that used to take days.
How an SBOM Works Under the Hood
An SBOM isn’t a single format: two dominant standards exist, SPDX and CycloneDX, and both represent the same thing with different structures. SPDX was born at the Linux Foundation with a focus on software licensing and became the ISO/IEC 5962:2021 standard. CycloneDX was born inside the OWASP project with a focus on supply chain security and today natively supports VEX (Vulnerability Exploitability eXchange), a mechanism for declaring whether a known vulnerability actually affects your product.
Both formats model the same core concept: a component has a name, a version, and a unique identifier called a purl (package URL), like pkg:maven/org.apache.logging.log4j/[email protected]. The same scheme identifies packages from other ecosystems: pkg:npm/[email protected] for npm or pkg:pypi/[email protected] for Python. That uniformity is what lets a single vulnerability database, like the NVD’s, automatically cross-reference inventories that mix several languages in the same project.
| Format | When to Use It | Advantage | Limitation |
|---|---|---|---|
| SPDX | Legal and license compliance, contracts with the public sector | ISO/IEC 5962:2021 standard, models licenses in detail | More verbose syntax for representing vulnerabilities |
| CycloneDX | Supply chain security, integration with CVE scanners | Supports VEX natively and is lighter to generate in CI | Less focus on complex license metadata |
Most modern tools, including Syft, generate both formats with the same command line. The choice usually depends on what your client or regulator requires, not a technical limitation.
flowchart LR
A["checkout-api 2.4.1"] --> B["spring-boot 3.2.0"]
A --> C["log4j-core 2.14.1"]
B --> D["jackson-databind 2.15.2"]
C --> E["log4j-api 2.14.1"]
subgraph "Transitive dependencies"
D
E
end
That diagram is exactly the problem an SBOM solves: log4j-core might never appear in your direct pom.xml, but instead hang three levels below a dependency you did declare. Without an inventory of the full tree, that library is invisible until someone specifically asks about it.
💭 Key point: the purl is the identifier that connects your SBOM to vulnerability databases. Without a consistent purl, cross-referencing the inventory against known CVEs becomes manual work.
Practical Examples
The simplest way to see an SBOM in action is to generate one. Syft is an open source tool from Anchore that supports more than 20 package ecosystems (npm, pip, Maven, Go modules, gems, Alpine APK, RPM, and more) and produces SPDX or CycloneDX output with a single command.
syft dir:. -o cyclonedx-json > sbom.json
That command scans the current directory, detects every dependency manifest it finds (package-lock.json, go.sum, Pipfile.lock, and so on), and writes an SBOM in CycloneDX JSON format. The result looks like this:
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"components": [
{
"type": "library",
"name": "log4j-core",
"version": "2.14.1",
"purl": "pkg:maven/org.apache.logging.log4j/[email protected]"
}
]
}
That fragment is, literally, one inventory entry: a library called log4j-core, version 2.14.1, identified by its purl. Multiply that entry by every component in the project (in a real application it’s easily hundreds, counting transitive ones) and you have the complete SBOM.
The next step is cross-referencing that file against a vulnerability database. Grype, from the same team as Syft, reads Syft’s output directly:
syft packages registry:ghcr.io/acme/checkout-api:2.4.1 -o spdx-json > checkout-api.spdx.json
grype sbom:checkout-api.spdx.json --fail-on high
The first line generates the SBOM directly from a container image in a remote registry, without downloading it manually. The second scans that SBOM and returns a nonzero exit code if it finds a high or critical severity vulnerability, useful for stopping a CI pipeline before publishing a vulnerable image.
Getting Started: Installing and Generating Your First SBOM
Installing Syft and Grype takes less than a minute on Linux or macOS:
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
To confirm the installation worked and see the exact version installed:
syft version
grype version
With both tools ready, the minimal flow is: generate the SBOM from the code or image, scan it, and decide whether the result blocks the build. Anchore’s official action does both steps in GitHub Actions:
name: sbom
on: [push]
jobs:
generate-sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate SBOM with Syft
uses: anchore/sbom-action@v0
with:
path: .
format: cyclonedx-json
output-file: sbom.cdx.json
- name: Scan with Grype
uses: anchore/scan-action@v3
with:
sbom: sbom.cdx.json
fail-build: true
severity-cutoff: high
That pipeline runs on every push, generates the SBOM, scans it, and fails the build if anything of high or critical severity shows up. To verify the generated file has real content, a simple check with jq counts the components:
jq '.components | length' sbom.cdx.json
If that number is 0 or suspiciously low compared to the project’s actual size, something in the manifest detection failed before you trust the scan result.
💡 Tip: run syft dir:. -o table the first time instead of JSON. The table format is much easier to read at a glance to verify Syft detected every expected manifest before automating anything.
Real-World Use Cases
The most cited use case is incident response. When a new CVE appears in a popular library, a team with centralized SBOMs answers the question “where does this run?” with a query, not a manual project-by-project audit.
sequenceDiagram
participant Eq as Security team
participant Reg as Central SBOM registry
participant Sist as Production systems
Eq->>Reg: searches "log4j-core 2.14.1" across all SBOMs
Reg-->>Eq: returns the systems that contain it
Eq->>Sist: applies the patch only where it applies
Note over Eq,Sist: without an SBOM, this search takes days of manual auditing
Another use case is contractual: since Executive Order 14028, any vendor selling software to the US federal government must be able to deliver an SBOM on request. Large private companies started requiring the same from their suppliers, for the same reason they require a certificate of origin from a hardware vendor.
A third case, less discussed, is licensing: an SBOM in SPDX format lets a legal team automatically audit whether any dependency brought in a copyleft license incompatible with a proprietary product, without relying on a developer having declared it by hand.
A fourth case, increasingly common, comes up in mergers and acquisitions: before buying a software company, the technical due diligence team requests the product’s complete SBOM to assess licensing risk and inherited vulnerabilities before signing.
Common Mistakes and Best Practices
The most common mistake is generating the SBOM once, at project launch, and never updating it again. An outdated SBOM is worse than having no SBOM at all: it gives a false sense of coverage while the actual dependency tree has already changed. The correct practice is to generate it on every build, as part of the CI pipeline.
The second mistake is scanning only direct dependencies. Most vulnerabilities exploited in practice, including Log4Shell, live in transitive dependencies that nobody declared by hand. An SBOM generated with Syft solves this because it walks the entire tree. If the team builds the inventory manually from the top-level manifest, it loses exactly what matters most.
The third mistake is treating every Grype result as an urgent alert without checking whether the vulnerability is exploitable in the application’s real context. Often a vulnerable library is present but the affected function is never called. That’s where VEX comes in: explicitly declaring that a known vulnerability doesn’t apply keeps the team from wasting hours reviewing false positives every week.
The fourth mistake is forgetting components that aren’t packages from a traditional manager: the base container image, operating system packages installed with apt or apk, manually compiled binaries. Syft detects those too, but only if it’s pointed at the final image and not just the repository’s source code.
⚠️ Watch out: an SBOM generated from source code may not match what’s actually running in production, if the build process adds or replaces dependencies at deploy time. Generate the SBOM as close as possible to the final artifact, the container image, not the repository.
Comparison with Alternatives
Syft and Grype aren’t the only possible combination. The choice depends on whether you already have a container scanner installed or prefer separate tools for generating and scanning.
| Tool | When to Use It | Advantage | Limitation |
|---|---|---|---|
| Syft | Generating the initial SBOM for a repo, image, or directory | Supports SPDX and CycloneDX across more than 20 package ecosystems | Doesn’t scan vulnerabilities on its own |
| Grype | Scanning an existing SBOM against CVE databases | Integrates directly with Syft’s output with no conversion needed | Depends on the vulnerability database being up to date |
| Trivy | A single command to generate and scan at once | Also covers infrastructure-as-code configuration and exposed secrets | The SBOM it generates is less configurable than Syft’s |
| sbom-tool (Microsoft) | Organizations already operating on Azure DevOps | Native integration with that pipeline | Lower adoption outside the Microsoft ecosystem |
For teams already using Trivy to scan container images, adding Syft and Grype can be redundant. The combination makes sense when you need to explicitly separate the step of generating the inventory from the step of deciding what to do with it, for example to archive the SBOM regardless of the scan result.
Going Deeper
An SBOM generated from source code is a static SBOM: it describes what the build declares, not necessarily what runs in memory. A dynamic SBOM is generated by inspecting a running process and captures dynamically loaded dependencies that static analysis might miss, like plugins loaded at runtime. The two are complementary: the static one is cheap to generate on every build, the dynamic one is more expensive but more faithful to production reality.
The other advanced concept is VEX (Vulnerability Exploitability eXchange). Scanning an SBOM against a CVE database almost always produces a long list of matches by package version, regardless of whether the vulnerable code actually runs. VEX is a separate document, in CycloneDX or CSAF format, where a product’s maintainer declares the status of each CVE: affected, not_affected, fixed, or under_investigation. Combining SBOM with VEX is what reduces the false-positive fatigue that hits any team that starts scanning seriously.
In practice, a mid-sized Node.js project can easily exceed several hundred dependencies counting transitive ones, the vast majority never manually reviewed by anyone on the team. That’s the real scale of the problem an SBOM makes visible: it’s not a list of ten libraries the team remembers by heart, it’s a graph no single person can keep updated by hand.
Finally, an unsigned SBOM is just another file someone could replace. The Sigstore project lets you sign the SBOM in the same pipeline step where it’s generated, so anyone can verify that specific inventory corresponds exactly to that build and wasn’t altered afterward.
📖 Summary on Telegram: View summary
Your next step: run syft dir:. -o table on a real repository you have on hand and count how many transitive dependencies show up that you didn’t recognize.
Frequently Asked Questions
Does an SBOM replace a vulnerability scanner?
No. The SBOM is the inventory; the scanner, like Grype, is what cross-references that inventory against a database of known CVEs. Without the SBOM, the scanner doesn’t have a complete map of what to look for, especially in transitive dependencies.
SPDX or CycloneDX, which should I choose if I don’t have an external requirement?
If the main goal is security and vulnerability scanning, CycloneDX tends to be more straightforward thanks to its native VEX support. If the goal is license compliance, SPDX models that domain in more detail.
Do you need to generate the SBOM on every build, or is once a month enough?
Generating it on every build is the recommended practice. Dependencies change with every library update, and an SBOM that’s weeks out of date can miss exactly the vulnerable version added yesterday.
Is an SBOM useful for your own code, or only for third-party dependencies?
Mainly for third parties, which is where supply chain risk lives. Tools like Syft also list the project’s main component, but the real value is in mapping everything you didn’t write yourself.
What happens if a dependency doesn’t have a recognizable purl?
It still shows up in the SBOM with whatever metadata is available: name, version, location in the tree. But automatically cross-referencing it against CVE databases becomes less reliable, and it’s a good sign that dependency deserves manual review.
References
- CycloneDX: official format specification and schema examples.
- SPDX: official specification of the ISO/IEC 5962:2021 standard.
- Syft on GitHub: source code, installation documentation, and supported ecosystems.
- Grype on GitHub: documentation for the SBOM vulnerability scanner.
- Software bill of materials on Wikipedia: historical context and regulatory adoption.
- CISA: the US agency driving SBOM adoption following Executive Order 14028.
📱 Enjoying this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.
Imagen destacada: Foto de Artturi Jalli en Unsplash
0 Comments