Software supply chain attacks hit a new pace in 2026. In early August, a self-propagating npm worm known as ChainDrop tore through more than 400 packages and racked up billions of monthly downloads before most teams even knew the libraries were compromised. A few weeks earlier, attackers hijacked the release pipeline for four core AsyncAPI GitHub repositories and slipped trojanized packages into the wild. Neither incident was exotic. Both were the kind of thing a Software Bill of Materials (SBOM) is built to catch fast, once you actually have one wired into your pipeline.
An SBOM is a machine-readable inventory of every component, library, and dependency inside a piece of software, plus the relationships between them. Think of it as an ingredient label for code. When a vulnerability like a malicious npm version or a poisoned Rust crate turns up, an SBOM lets you query “do any of my applications contain this exact version?” in seconds instead of days. That single capability is why regulators on both sides of the Atlantic have turned SBOMs from a nice-to-have into a paper trail auditors will ask for.
This tutorial walks through generating, validating, and operationalizing SBOMs using the same open-source tools that dominate the space right now: Syft, Grype, Trivy, and Dependency-Track. By the end you will have a working pipeline that produces a CycloneDX or SPDX SBOM on every build, scans it for known vulnerabilities, and stores the results somewhere your security team can actually query.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Why SBOMs matter more in 2026 than they did a year ago
Two regulatory tracks converged this year. In the US, the Cybersecurity and Infrastructure Security Agency (CISA) published its 2026 Minimum Elements for a Software Bill of Materials in July, formally replacing the 2021 NTIA baseline that most tooling still targets. The new guidance roughly doubles the required data set, splitting it into SBOM Metadata (fields describing the document itself, like the tool name, tool version, and a signature from the SBOM author) and Component Data (fields describing each piece of software, including cryptographic hashes and license identifiers). If your organization sells to the federal government or works with a contractor who does, this is the baseline that now matters, not the 2021 version most existing tutorials still reference.
In the European Union, the Cyber Resilience Act (CRA) introduces the first legal requirement for manufacturers to generate, maintain, and share SBOMs for any product with digital elements sold in the EU. Reporting obligations, including notification of actively exploited vulnerabilities, start September 11, 2026. The core SBOM requirement itself becomes fully enforceable on December 11, 2027, which makes 2026 the year organizations are expected to get their generation and lifecycle processes in shape rather than scrambling the month before enforcement hits. The European Union Agency for Cybersecurity (ENISA) reported in its 2026 SBOM Adoption State of Play that 78% of surveyed organizations had already begun implementation work, with 79% expecting to hit the required maturity level before the deadline.
The uncomfortable number sitting underneath all of that policy activity: an empirical scan of more than 26,000 popular GitHub repositories found that under 1% actually ship a policy-compliant SBOM file today. Everyone is talking about SBOMs. Almost nobody’s open-source dependency tree is actually documented. That gap is the opportunity this tutorial closes. The official guidance from the National Institute of Standards and Technology and the European Commission’s Cyber Resilience Act page are worth bookmarking directly, since minimum-element requirements get revised roughly once a year and third-party summaries lag behind.
Recent supply chain incidents SBOMs would have caught faster
The abstract case for SBOMs gets a lot more concrete once you line up what actually happened this year. The ChainDrop npm worm, discovered in early August 2026, compromised more than 400 packages and over 1,300 versions, including widely used libraries such as keyv and flat-cache, reaching packages with multi-billion monthly downloads in under four hours of propagation. An organization with an up-to-date SBOM inventory could query every tracked application for the exact compromised versions in minutes; without one, the standard response is a frantic, ecosystem-wide grep through lockfiles across every repository the company owns.
Two other incidents from mid-2026 make the same point from different ecosystems. In July, attackers compromised the release pipeline for four core AsyncAPI GitHub repositories and published five trojanized npm packages, including tampered versions of @asyncapi/generator and @asyncapi/specs. That same month, a campaign dubbed SleeperGem injected malicious RubyGems, including tampered versions of a Git credential manager gem, to deliver secondary payloads to developer machines. In August, a separate attack targeted the Rust ecosystem through a malicious proc-macro1 crate that reconstructed obfuscated command-and-control addresses and disabled TLS verification on infected machines. None of these three incidents involved the same package registry, the same language, or the same attacker group. What they share is that in each case, the organizations best positioned to respond quickly were the ones that could answer “do we use this exact component and version” without manually inspecting every repository by hand.
Prerequisites and versions
You do not need specialized hardware for this tutorial, just a machine that can run containers and a shell. Here is what to have installed before you start, with the current versions as of September 2026:
| Tool | Version used in this tutorial | Purpose | License |
|---|---|---|---|
| Syft | v1.51.1 | Generates the SBOM from source, container images, or filesystems | Apache 2.0 (free) |
| Grype | v0.118.0 | Scans an SBOM or image for known vulnerabilities | Apache 2.0 (free) |
| Trivy | v0.74.0 | All-in-one scanner; also generates and consumes SBOMs | Apache 2.0 (free) |
| Dependency-Track | 5.1.0 | Self-hosted platform to store, track, and alert on SBOM data over time | Apache 2.0 (free) |
| Docker or Podman | Docker 27.x / Podman 5.x | Runs Dependency-Track and any containerized targets you scan | N/A |
| Git | 2.40+ | Version control for the sample project | N/A |
| Node.js | 20 LTS or 22 LTS | Runs the sample application used in this walkthrough | N/A |
You will also want a GitHub account if you plan to wire SBOM generation into CI/CD, and roughly 4GB of free RAM if you run Dependency-Track locally via Docker Compose. Everything here also works with GitLab CI, Jenkins, or CircleCI with minor syntax changes to the pipeline definition.
Step 1: Understand the two SBOM formats you will actually encounter
Almost every tool you touch will output one of two formats: CycloneDX or SPDX. Both are accepted under the EU Cyber Resilience Act’s Annex I Part II(1) guidance as “commonly used machine-readable formats.” Picking the wrong one for your downstream tooling is the single most common early mistake in SBOM adoption, so it is worth understanding the difference before generating anything.
CycloneDX, maintained by OWASP, is now at spec version 1.7 (ratified October 2025) and was purpose-built for application security use cases: vulnerability correlation, license compliance, and dependency graphs. A CycloneDX 2.0 revision is in progress with Ecma ratification expected around December 2026, extending the format beyond SBOMs into threat models and provenance attestations.
SPDX (Software Package Data Exchange) is an ISO-standardized format (ISO/IEC 5962:2021), maintained at spdx.dev and currently at version 3.0.1, that grew out of license-compliance tooling and is broader in scope, covering hardware and AI/ML model bills of materials in addition to software. If your organization needs to satisfy a legal or procurement requirement that explicitly cites SPDX, use SPDX. If you are optimizing for vulnerability scanning speed and tooling compatibility with Grype, Trivy, and Dependency-Track, CycloneDX (full specification maintained on GitHub) has the deeper ecosystem right now. This tutorial generates both so you can see the structural differences directly.
Step 2: Install Syft
Syft, built by Anchore and hosted at github.com/anchore/syft, is the closest thing the industry has to a default SBOM generator. It can scan a local directory, a Git repository, a running container, or an OCI image pulled straight from a registry. Install it with the official script:
# macOS / Linux
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Verify installation
syft version
On macOS you can also install via Homebrew (brew install syft), and on Windows via Scoop (scoop install syft) or by downloading the release binary directly from the Syft GitHub releases page. The expected output from syft version looks like this:
Application: syft
Version: 1.51.1
BuildDate: 2026-08-27T14:02:11Z
GitCommit: a3f9c21
GitDescription: v1.51.1
Platform: linux/amd64
GoVersion: go1.23.4
Compiler: gc
Step 3: Generate your first SBOM from a directory
Clone or navigate to any Node.js, Python, Go, Java, or Rust project. For this tutorial we will use a small Express.js sample app. Run Syft against the project root:
git clone https://github.com/expressjs/express-generator-sample sbom-demo
cd sbom-demo
npm install
# Generate a CycloneDX SBOM
syft dir:. -o cyclonedx-json=sbom-cyclonedx.json
# Generate an SPDX SBOM from the same source
syft dir:. -o spdx-json=sbom-spdx.json
Syft walks the project, reads package-lock.json (or requirements.txt, go.sum, Cargo.lock, and so on depending on ecosystem), and resolves every direct and transitive dependency into a structured component list. On a mid-sized Node.js project with 150-200 dependencies, this typically completes in under 10 seconds. The terminal output during a scan looks like this:
✔ Indexed file system sbom-demo
✔ Cataloged contents 327 packages, 4 executables
├── ✔ Package cataloger: javascript-lock-cataloger 312 packages
├── ✔ Package cataloger: binary-cataloger 4 packages
└── ✔ Package cataloger: file-metadata-cataloger 11 packages
Open sbom-cyclonedx.json and you will see a structured document with a components array. Each entry includes the package name, version, a PURL (package URL) identifier, and where available, a license string:
{
"bomFormat": "CycloneDX",
"specVersion": "1.7",
"serialNumber": "urn:uuid:3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"version": 1,
"components": [
{
"type": "library",
"name": "express",
"version": "4.21.2",
"purl": "pkg:npm/[email protected]",
"licenses": [{ "license": { "id": "MIT" } }]
}
]
}
Step 4: Generate an SBOM from a container image
Source-level SBOMs miss anything baked into the container: the base OS packages, the language runtime itself, and any binaries installed via apt, apk, or yum during the build. To capture the full picture, point Syft at the built image instead of the source directory:
# Build the image first
docker build -t sbom-demo:latest .
# Generate an SBOM from the image
syft sbom-demo:latest -o cyclonedx-json=sbom-image-cyclonedx.json
# Or pull directly from a registry without a local build
syft registry:node:22-alpine -o cyclonedx-json=sbom-base-image.json
A container-level scan surfaces two to three times more components than a source-only scan on a typical Node or Python image, because it picks up the base OS’s package manager entries (Debian’s dpkg, Alpine’s apk) alongside your application dependencies. This is the SBOM you actually want in production, since it reflects what is running, not just what you wrote.
Step 5: Install Grype and scan the SBOM for known vulnerabilities
An SBOM by itself is just an inventory. Pair it with Grype, also from Anchore and hosted at github.com/anchore/grype, to cross-reference every component against known vulnerability databases (NVD, GitHub Security Advisories, and distro-specific feeds).
# Install Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Scan a previously generated SBOM instead of re-scanning the image
grype sbom:./sbom-image-cyclonedx.json -o table
Feeding Grype an existing SBOM instead of scanning the image directly is faster and lets you decouple “generate the inventory” from “check it against threat intel,” which matters once you are running this on every commit. Sample output:
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
libssl3 3.0.11-r0 3.0.16-r0 apk CVE-2026-1234 High
express 4.21.0 4.21.2 npm GHSA-rv95-896h-c2vc Medium
busybox 1.36.0 1.36.1-r2 apk CVE-2026-9981 Critical
3 vulnerabilities found: 1 Critical, 1 High, 1 Medium
Grype also supports a --fail-on flag, which is what makes it useful as a CI gate rather than just a reporting tool: grype sbom:./sbom-image-cyclonedx.json --fail-on critical will exit with a non-zero status code if any critical-severity CVE is present, breaking the build before a vulnerable image ships.
Step 6: Try Trivy as a single-tool alternative
Trivy, maintained by Aqua Security, folds SBOM generation and vulnerability scanning into a single binary, which some teams prefer over running Syft and Grype separately. It is currently at v0.74.0.
# Install Trivy (Linux)
sudo apt-get install trivy
# Generate an SBOM AND scan it in one command
trivy image --format cyclonedx --output trivy-sbom.json sbom-demo:latest
trivy sbom trivy-sbom.json --severity CRITICAL,HIGH
Trivy also scans infrastructure-as-code files (Terraform, Kubernetes manifests) and detects hardcoded secrets in the same pass, which Syft and Grype do not do. The tradeoff is that Trivy’s SBOM output is somewhat less detailed on transitive dependency relationships than Syft’s, so many teams run Syft for the SBOM itself and Trivy as a secondary secrets/misconfiguration check. Neither approach is wrong; pick based on whether you want one tool to learn or two tools each doing one job well.
Step 7: Stand up Dependency-Track to store SBOMs over time
A one-off SBOM answers “what is in this build right now.” The regulatory requirement, and the actual security value, comes from tracking how your dependency tree changes over time and getting alerted the moment a component you already shipped turns out to be vulnerable. Dependency-Track, now at version 5.1.0, is the open-source platform built for exactly that. Run it via Docker Compose:
mkdir dtrack && cd dtrack
curl -LO https://dependencytrack.org/docker-compose.yml
docker compose up -d
# Check that both services are healthy (takes 1-2 minutes on first boot)
docker compose ps
Once running, the UI is available at http://localhost:8080. The default login is admin / admin, and you will be forced to set a new password on first login. Dependency-Track 5.1 adds native ingestion for CycloneDX 1.7 and built-in tracking of Known Exploited Vulnerabilities (KEV), pulling directly from CISA’s KEV catalog so you can prioritize fixes for bugs that are being actively exploited in the wild, not just theoretically dangerous.
Step 8: Create a project and upload your SBOM
Inside the Dependency-Track UI, create a new project matching your application name and version. Then upload the CycloneDX file you generated in Step 3 or Step 4, either through the UI or via the API:
# Get an API key from Administration > Access Management > Teams
export DTRACK_API_KEY="odt_xxxxxxxxxxxxxxxxxxxxx"
export DTRACK_URL="http://localhost:8080"
curl -X POST "$DTRACK_URL/api/v1/bom" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-H "Content-Type: multipart/form-data" \
-F "autoCreate=true" \
-F "projectName=sbom-demo" \
-F "projectVersion=1.0.0" \
-F "[email protected]"
Within a minute or two, Dependency-Track will have processed the BOM, matched every component against its vulnerability database, and populated the project dashboard with a risk score, a license summary, and a list of any flagged CVEs. This is the view your security team should be checking weekly, not a spreadsheet someone updates by hand.
Step 9: Automate SBOM generation in GitHub Actions
Manual generation defeats the point. The CISA 2026 Minimum Elements explicitly call for automation support as one of the core requirements, and 74% of organizations surveyed in 2026 reported at least partially automating per-release SBOM generation. Here is a workflow that generates an SBOM on every push to main, scans it with Grype, and fails the build on critical vulnerabilities:
name: SBOM Generation and Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Syft
run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- name: Install Grype
run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
- name: Generate SBOM
run: syft dir:. -o cyclonedx-json=sbom.json
- name: Scan SBOM for critical vulnerabilities
run: grype sbom:./sbom.json --fail-on critical
- name: Upload SBOM to Dependency-Track
run: |
curl -X POST "${{ secrets.DTRACK_URL }}/api/v1/bom" \
-H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \
-F "autoCreate=true" \
-F "projectName=${{ github.repository }}" \
-F "projectVersion=${{ github.sha }}" \
-F "[email protected]"
- name: Archive SBOM artifact
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
Store DTRACK_URL and DTRACK_API_KEY as encrypted repository secrets rather than hardcoding them. Archiving the SBOM as a build artifact, in addition to uploading it to Dependency-Track, gives you a durable per-build record that satisfies the CISA requirement to timestamp and retain SBOM data.
Step 10: Sign your SBOM to satisfy the 2026 CISA baseline
One of the genuinely new requirements in CISA’s 2026 Minimum Elements is an SBOM Author Signature, a cryptographic signature proving who generated the document and that it has not been tampered with since. The open-source project Cosign, part of the Sigstore ecosystem, is the most common way to add this without standing up your own PKI:
# Install cosign
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64"
chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign
# Generate a keypair (or use keyless signing with an OIDC identity in CI)
cosign generate-key-pair
# Sign the SBOM
cosign sign-blob --key cosign.key sbom.json --output-signature sbom.json.sig
# Anyone can later verify it came from you and was not modified
cosign verify-blob --key cosign.pub --signature sbom.json.sig sbom.json
In a CI environment, use Cosign’s keyless signing mode with your CI provider’s OIDC token instead of managing a long-lived private key. This avoids the single biggest operational headache with signing: key storage and rotation.
Step 11: Query your SBOMs when the next supply chain attack drops
This is the step that justifies everything above it. When a package like the compromised versions involved in the ChainDrop npm worm or the SleeperGem RubyGems campaign gets disclosed, the difference between an organization with SBOMs and one without is measured in hours versus days. With Dependency-Track, you can search the “Portfolio” view across every tracked project for a specific PURL:
# Search all tracked projects for a specific compromised package via API
curl -s "$DTRACK_URL/api/v1/component/identity?purl=pkg:npm/[email protected]" \
-H "X-Api-Key: $DTRACK_API_KEY" | jq '.[] | {project: .project.name, version: .version}'
That single query tells you, across your entire application portfolio, exactly which services need an emergency patch, instead of paging every team and asking them to manually check their package-lock.json files. This is also the workflow security teams increasingly demand from vendors: if a supplier cannot answer “do you use this component” within the hour, that is now treated as a supply chain risk indicator in its own right.
Step 12: Set up alerting for newly disclosed vulnerabilities
SBOMs decay the moment you stop updating them, since new CVEs get disclosed against components you already shipped months ago. Configure Dependency-Track to notify your team automatically instead of relying on someone remembering to re-scan:
# In the Dependency-Track UI:
# Administration > Notifications > Create Alert
# Publisher: Slack (or Microsoft Teams, email, webhook)
# Notification scope: PORTFOLIO
# Trigger on: NEW_VULNERABILITY, NEW_VULNERABLE_DEPENDENCY
# Notification level: WARNING or higher
With this configured, the moment Dependency-Track’s vulnerability feed updates overnight and flags a component already in your portfolio, a message lands in your team’s Slack channel automatically, no manual re-scan required. Pair this with the KEV-tracking feature in Dependency-Track 5.1 to distinguish “theoretically vulnerable” from “actively being exploited right now,” which should drive very different response timelines.
Common pitfalls when generating and managing SBOMs
These are the mistakes that show up repeatedly once teams move past a single test SBOM and try to run this at scale.
- Scanning source only, never the built artifact. A source-level SBOM misses base OS packages, runtime binaries, and anything installed during the Docker build. Always generate at least one SBOM from the final container image, not just the repository.
- Picking a format your downstream tools cannot read. Verify that your vulnerability scanner, your compliance platform, and any customer-facing SBOM delivery requirement all agree on CycloneDX versus SPDX before you standardize. Switching formats after six months of pipeline history is painful.
- Treating SBOM generation as a one-time compliance checkbox. A study of 2025-2026 supply chain incidents found that stale SBOMs, generated once at release and never refreshed, were functionally useless during actual incident response because the component list no longer matched what was deployed.
- Only capturing direct dependencies. The CRA’s minimum bar is top-level dependencies, but the vast majority of real-world vulnerabilities in 2026 supply chain attacks were in transitive dependencies two or three levels deep. Configure Syft and Trivy to resolve the full dependency tree, not just what is declared in your manifest.
- No signature or provenance attached. An unsigned SBOM is trivially easy to spoof or tamper with. If you skip Step 10, you technically have an SBOM but it fails the CISA 2026 baseline and provides weaker assurance to anyone consuming it.
SBOM tool comparison: open source versus commercial
Syft, Grype, and Trivy handle generation and scanning for free with no seat limits, which covers most engineering teams. Larger organizations often add a commercial platform on top for portfolio-wide dashboards, compliance reporting, and vendor SBOM ingestion. Here is how the current pricing landscape breaks down:
| Tool | Type | Starting price | Best for |
|---|---|---|---|
| Syft + Grype | Open source | $0 | Generation and scanning in CI/CD, unlimited use |
| Trivy | Open source | $0 | Combined SBOM, secrets, and IaC scanning in one binary |
| Dependency-Track (self-hosted) | Open source | $0 (infrastructure cost only) | Portfolio-wide tracking and alerting |
| Snyk | Commercial | Free tier; Team plan from $25/developer/month | Teams wanting SBOM plus developer-friendly fix suggestions |
| FOSSA | Commercial | Free tier (5 projects); Business from $20/project/month | License compliance alongside SBOM generation |
| Anchore Enterprise | Commercial | List pricing around $34,500/year for a single-host deployment | Large enterprises needing policy enforcement at scale |
| JFrog Xray | Commercial (bundled) | Included in JFrog Platform Pro X, from roughly $150/month | Organizations already standardized on JFrog Artifactory |
Troubleshooting common SBOM generation issues
Here are the errors and confusing outputs you are most likely to run into, and how to resolve each one.
- Syft reports zero components on a Node.js project. This almost always means Syft could not find a lockfile. Run
npm installornpm cifirst to generatepackage-lock.json, since Syft parses the lockfile rather thanpackage.jsonalone to get resolved, exact versions. - Grype scan hangs or times out on first run. Grype downloads and caches a local vulnerability database (several hundred MB) on first execution. Run
grype db updatemanually first if you are on a slow connection, and checkgrype db statusto confirm the database downloaded successfully. - Dependency-Track shows “BOM_PROCESSING_FAILED” after upload. This typically means the CycloneDX file is malformed or references a spec version Dependency-Track does not yet support. Validate the file first with
cyclonedx-cli validate --input-file sbom.jsonbefore uploading. - Container image SBOM is missing expected OS packages. If you built a multi-stage Docker image, make sure you are scanning the final stage, not an intermediate build stage. Syft can only see what is present in the layer you point it at.
- Vulnerability counts differ wildly between Grype and Trivy on the same image. The two tools pull from overlapping but not identical vulnerability databases and apply different severity-scoring logic. This is expected; many teams run both and treat any CVE flagged by either tool as worth investigating rather than trying to reconcile the exact counts.
- Cosign signature verification fails after signing. Confirm you are verifying against the matching public key (
cosign.pub) generated alongside the private key used to sign, and that the SBOM file has not been re-formatted or had whitespace changed after signing, which invalidates the signature. - GitHub Actions workflow fails on the Grype fail-on step even with no critical CVEs visible. Check for CVEs in transitive dependencies that do not show in a shallow scan; run
grype sbom:./sbom.json -o json | jq '.matches[].vulnerability.severity'to see the full raw match list Grype used to make its decision. - Dependency-Track alerts are not firing despite new CVEs appearing in the dashboard. Double-check the notification rule’s scope is set to PORTFOLIO rather than a single project, and confirm the publisher (Slack, Teams, webhook) credentials have not expired.
Advanced tips for scaling SBOM management
Once the basic pipeline is running, a few refinements separate teams that treat SBOMs as a real security control from teams that generate them purely to check a compliance box.
First, generate SBOMs at multiple points in the pipeline, not just at final release. A source-level SBOM at pull-request time catches a newly introduced vulnerable dependency before merge; a container-level SBOM at deploy time captures what is actually running. Comparing the two over time also reveals dependency drift, cases where a transitive dependency silently upgraded between builds without anyone noticing.
Second, integrate SBOM data with your incident response runbook explicitly, not as an afterthought. When your security team receives a new critical CVE alert, the runbook should say “query Dependency-Track for this PURL across all projects” as an explicit first step, not something someone has to remember to do under pressure.
Third, if you distribute software to customers or government buyers, build an automated process to generate a customer-facing SBOM export on every release, formatted to the specific minimum elements your buyer requires. Manually preparing SBOM deliverables for each customer request does not scale past a handful of enterprise contracts.
Fourth, watch the CycloneDX 2.0 transition closing out 2026. It extends the format beyond a static component list into threat models, controls, and provenance attestations, which means the SBOM tooling landscape is likely to shift again within the next 12 months. Building your pipeline around Syft and Grype’s standard CLI flags rather than deeply custom scripting will make that migration far less painful when it lands.
SBOMs beyond application code: AI models and hardware
SBOMs started as a way to inventory application dependencies, but the scope is widening fast. SPDX 3.0 explicitly added support for AI and machine learning bills of materials (sometimes shortened to AIBOM), covering training data provenance, model weights, and fine-tuning lineage rather than just code packages. If your team ships a product that bundles a third-party model or fine-tunes an open-weight base model, treating that model the same way you treat an npm dependency, tracked, versioned, and scanned for known issues, is quickly becoming an expectation rather than an edge case. The same logic extends to hardware: a growing number of CRA-adjacent guidance documents ask device manufacturers to maintain a hardware bill of materials alongside the software one, since a vulnerable firmware component buried in a purchased hardware module is functionally the same supply chain risk as a vulnerable open-source library.
None of this changes the workflow covered in this tutorial in a fundamental way. Syft and the CycloneDX and SPDX formats are both extending their existing schemas to cover these new component types rather than requiring a separate toolchain, which is exactly the kind of format stability that makes standardizing on CycloneDX or SPDX now, rather than a proprietary in-house format, worth the short-term setup cost.
Complete working project: SBOM pipeline from clone to alert
Putting every step together, here is the full sequence for a working end-to-end SBOM pipeline you can run today, from a fresh clone through automated alerting:
# 1. Install the tools
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
# 2. Stand up Dependency-Track
mkdir dtrack && cd dtrack
curl -LO https://dependencytrack.org/docker-compose.yml
docker compose up -d
sleep 90 # first boot takes a minute or two
# 3. Build your application image
cd ../sbom-demo
docker build -t sbom-demo:latest .
# 4. Generate the SBOM from the built image
syft sbom-demo:latest -o cyclonedx-json=sbom.json
# 5. Scan locally and fail fast on critical issues
grype sbom:./sbom.json --fail-on critical
# 6. Sign it
cosign sign-blob --key cosign.key sbom.json --output-signature sbom.json.sig
# 7. Upload to Dependency-Track for ongoing tracking
curl -X POST "http://localhost:8080/api/v1/bom" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-F "autoCreate=true" \
-F "projectName=sbom-demo" \
-F "projectVersion=1.0.0" \
-F "[email protected]"
echo "Pipeline complete. Check http://localhost:8080 for the risk dashboard."
Wrap steps 3 through 7 into your CI/CD pipeline using the GitHub Actions example from Step 9, and you have a self-sustaining system: every commit produces a signed, tracked SBOM, every newly disclosed CVE against a component you use triggers a Slack alert, and every audit or customer request for an SBOM can be answered by pulling the latest artifact rather than starting from scratch.
Frequently asked questions
Do I need an SBOM if I am not selling to the US federal government or the EU?
Not legally, yet, for most private-sector companies outside those markets. But the incentive to have one is independent of regulation: an SBOM cuts incident response time for supply chain attacks from days to minutes, and an increasing number of enterprise customers now require a current SBOM as part of vendor security questionnaires regardless of where they operate.
Which format should I standardize on, CycloneDX or SPDX?
CycloneDX has the stronger ecosystem for vulnerability scanning and is the default output for Syft, Grype, Trivy, and Dependency-Track. Use SPDX only if a specific legal, procurement, or customer requirement explicitly demands it.
Can I generate an SBOM without Docker?
Yes. Syft’s dir: mode scans a source directory directly with no container involved, which is sufficient for source-level dependency tracking. You only need a container when you want to capture what is baked into the final runtime image, including base OS packages.
How often should I regenerate an SBOM?
On every build that reaches a shared branch or gets deployed, at minimum. A CISA-aligned baseline expects SBOMs to be kept current throughout the product’s life cycle, which in practice means wiring generation into CI/CD rather than running it manually on a schedule.
Does an SBOM replace vulnerability scanning?
No. An SBOM is the inventory; a scanner like Grype or Trivy is what cross-references that inventory against known vulnerabilities. You need both, and ideally the SBOM should be reusable across multiple scanning and compliance tools rather than regenerated separately for each one.
What is the difference between Grype and Trivy if they both scan for vulnerabilities?
Grype is purpose-built as a vulnerability scanner that consumes SBOMs (its own or third-party) and focuses narrowly on that job. Trivy is broader, combining SBOM generation, vulnerability scanning, secrets detection, and infrastructure-as-code misconfiguration scanning in one tool. Many teams run both since their vulnerability databases do not perfectly overlap.
Is Dependency-Track required, or can I just store SBOM files in a folder?
You can store raw files, but you lose the ability to query across your entire portfolio when a new vulnerability drops, and you lose automated alerting. For a single small project, files on disk are workable. For any organization tracking more than a handful of applications, a platform like Dependency-Track pays for itself the first time you need to answer “are we affected by this” across dozens of services at once.
What happens if my SBOM is incomplete or inaccurate?
Under the EU Cyber Resilience Act, manufacturers are required to keep SBOMs accurate and current as part of their vulnerability handling obligations; a stale or incomplete SBOM undermines the compliance value entirely and, more practically, gives your own security team false confidence during an actual incident. Treat SBOM accuracy as a build-quality gate, not a documentation afterthought.


