Skip to content
Go back

SBOMs and Supply Chain Security

By SumGuy 7 min read
SBOMs and Supply Chain Security

December 2021. You’re On Pager Duty.

Suddenly, Slack explodes. Log4Shell. A zero-day in log4j. In everything.

You have no idea what’s in your app. Dependencies of dependencies of dependencies, nested three, four, five layers deep. Does your Java microservice use log4j? Probably. Does it use it directly, or only transitively through some library you forgot you imported two years ago? Good luck finding out.

You’re staring at the dependency tree like it’s the final season of Lost, trying to trace causality backwards. Meanwhile, the internet is literally on fire, and your team is guessing.

An SBOM — a Software Bill of Materials — would have answered that question in seconds. A machine-readable list of every package in your app, every version, every hash. Like an ingredients label on a food product. You check what’s in your cereal before you feed it to your kids. Why are we not doing this with code?

What’s a Supply Chain Attack, Anyway?

You might think of supply chain attacks as nation-state stuff — which, fair, SolarWinds in 2020 was nation-state stuff. But they’re not rare. They’re the norm now.

The pattern is simple: attackers don’t break your code. They break someone else’s code that you depend on. You download it, you build it into your application, you ship it to production. Boom. You’re compromised.

Log4Shell was the wake-up call. It wasn’t “hidden” in your app on purpose — it was just there, doing its job, until someone discovered it could do something a little too much like its job. And because log4j is everywhere (it’s a fundamental Java logging library), everyone had to panic-patch simultaneously.

The real question isn’t “how do we prevent supply chain attacks?” It’s “how do we know what we’re shipping?

That’s where an SBOM comes in.

An SBOM Is Just an Ingredients Label

A Software Bill of Materials is exactly what it sounds like: a comprehensive list of every component, library, and dependency in your software. Package name, version, hash, license, known vulnerabilities.

It’s what you should have been documenting all along but didn’t, because nobody thought to ask until things started exploding.

There are two major SBOM formats: SPDX (older, document-focused, very detailed) and CycloneDX (newer, container-friendly, oriented toward DevSecOps tooling). In practice, CycloneDX is what you’ll use if you’re shipping containers. Grype, the vulnerability scanner we’ll talk about next, prefers CycloneDX. So do most modern CI/CD systems.

Syft: Generate Your SBOM

Syft is a command-line tool from Anchore that generates SBOMs from basically any source: a Docker image, a Kubernetes pod, a directory on disk, an OCI artifact. It’s fast, it’s reliable, and it’s open source.

Install it:

Terminal window
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

Generate an SBOM from a Docker image:

Terminal window
syft docker:nginx:latest -o json > nginx-sbom.json

The output is a structured JSON file listing every package in that image. For nginx, that’s probably a few dozen packages. For a Node app with hundreds of npm dependencies? Hundreds or thousands of entries.

Here’s what a small chunk looks like:

nginx-sbom.json (truncated)
{
"artifacts": [
{
"name": "openssl",
"version": "1.1.1w",
"type": "apk",
"purl": "pkg:apk/openssl@1.1.1w"
},
{
"name": "libc",
"version": "0.7.12",
"type": "apk",
"purl": "pkg:apk/libc@0.7.12"
}
],
"source": {
"type": "docker",
"target": "nginx:latest"
}
}

Each entry has a PURL (Package URL), version, and type. For CI/CD, you pipe that output into your artifact store so you can scan it later (or scan it immediately, right there in the build).

You can also generate SBOMs from directories:

Terminal window
syft /path/to/project -o cyclonedx > app-sbom.xml

Or from a running container:

Terminal window
syft 'docker://containerid' -o json > container-sbom.json

Syft auto-detects the package managers (npm, pip, cargo, go, maven, etc.) and builds the full dependency tree.

Grype: Scan for Known Vulnerabilities

Now that you have a complete list of what’s in your software, the next step is obvious: are any of these packages known to be vulnerable?

Grype is Anchore’s vulnerability scanner. It takes an SBOM (or scans a container or directory directly) and cross-references every package against the NVD (National Vulnerability Database) and other sources. It tells you which CVEs affect you, their severity, and whether patches exist.

Install Grype:

Terminal window
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

Scan an SBOM:

Terminal window
grype sbom:nginx-sbom.json

Scan a Docker image directly:

Terminal window
grype docker:nginx:latest

Grype output looks like this:

✔ Vulnerability DB [updated]
✔ Scanned image
✔ Vulnerability scan
1 package [1 vulnerability]
CRITICAL CVE-2024-1234 openssl 1.1.1w
Fixed in: 1.1.1z
https://nvd.nist.gov/vuln/detail/CVE-2024-1234
INFO CVE-2020-5678 libc 0.7.12
No fix available

You can export results in multiple formats:

Terminal window
grype docker:nginx:latest -o json > scan-results.json
grype docker:nginx:latest -o sarif > scan-results.sarif

JSON for parsing, SARIF for GitHub security scanning. Dead simple.

Integrate Into CI

Here’s where it gets powerful: bake this into your build pipeline.

.github/workflows/sbom.yml
name: SBOM + Scan
on: [push]
jobs:
sbom:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Generate SBOM
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
syft docker:myapp:${{ github.sha }} -o cyclonedx > sbom.xml
- name: Scan for vulnerabilities
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
grype sbom:sbom.xml -o json > scan-results.json
- name: Fail if critical found
run: |
CRITICAL=$(jq '[.matches[] | select(.vulnerability.severity=="CRITICAL")] | length' scan-results.json)
if [ "$CRITICAL" -gt 0 ]; then
echo "Found $CRITICAL critical vulnerabilities"
exit 1
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: sbom-and-scan
path: |
sbom.xml
scan-results.json

Every build generates an SBOM, scans it, and fails if critical CVEs exist. Your artifacts are stored as evidence. When a new CVE is announced, you can query your artifact history: “do any of our builds from the past year contain this package?”

Attestation and Trust

Once you’re generating SBOMs, you’ll want to prove they haven’t been tampered with. Enter Cosign, a tool from Sigstore that cryptographically signs artifacts.

Terminal window
cosign sign-blob sbom.json --key cosign.key > sbom.json.sig
cosign verify-blob sbom.json --signature sbom.json.sig --key cosign.pub

Your SBOM is now signed. Consumers can verify it came from your build pipeline, unmodified. It’s a small thing, but it matters in a supply chain security story.

When Grype Finds Something

So Grype screams at you. “CRITICAL CVE in openssl 1.1.1w.”

Now what?

Triage. Not all critical CVEs affect you. Does the vulnerability require specific conditions? A particular API call? Read the CVE details. Maybe your app doesn’t hit that code path.

Upgrade. Usually the answer. Bump the dependency, re-run the SBOM, re-scan. Gone.

Accept the risk. Sometimes you can’t upgrade (legacy system, no patch available, you’re EOL). Document it. Know the risk. That’s better than not knowing.

Patch around it if the package maintainer won’t. Filter inputs, add controls, isolate the affected component. Not ideal, but better than ignoring it.

The point is: you know. You have a paper trail. You made a decision with full information. That’s the whole game.

The Boring Stuff Is Actually the Hard Part

Here’s the thing: SBOMs are now table stakes. Enterprises will demand them. SLSA compliance requires them. The tech is easy — Syft and Grype handle it in seconds.

The hard part is actually doing something when you find a vulnerability. The infrastructure, the testing, the approval process, the risk acceptance. That’s your real work.

But at least you’ll know what you’re shipping. And on the day the next Log4Shell hits, you won’t be guessing.

Generate an SBOM. Scan it. Commit it. Sleep better.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Self-Hoster's Disaster Recovery: When Everything Goes Wrong at Once
Next Post
Terraform vs Pulumi: Infrastructure as Code Without the YAML Nightmares

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts