SBOM and Bill of Materials Security Labs in 2026
We wrote 35 hands-on labs covering every aspect of Software Bill of Materials and its variants. Each lab walks through a real gap in visibility, shows what happens when that gap exists during a CVE.
Before we start, shoutout to a platform we built for YOU!
💎 Your next level in cybersecurity isn’t a dream, it’s a proactive roadmap.
HADESS AI Career Coach turns ambition into expertise:
→ 390+ clear career blueprints from entry-level to leadership
→ 490+ in-demand skill modules + practical labs
→ Intelligent AI(Not AI buzz, applied AI, promise!) tools + real-world expert coaches and scenarios
Master the skills that matter. Land the roles that pay. Build the future you want.
🔥 Start engineering your career →
https://career.hadess.io
The labs span seven areas: SBOM standards and formats (SPDX, CycloneDX, NTIA minimum elements, Package URL), SBOM generation tools (Syft, Trivy, cdxgen, Docker Scout, Microsoft sbom-tool), cloud-specific SBOM solutions (Amazon Inspector for ECR, Lambda, and EC2, Azure Defender, GCP Artifact Analysis), CI/CD SBOM pipelines (GitHub Actions, GitLab CI, Cosign attestation, Kyverno admission), SBOM consumption platforms (Dependency-Track, GUAC, DefectDojo, Grype), vulnerability triage with VEX (OpenVEX, CycloneDX VEX, CSAF advisories), and alternative bill of materials types (AI BOM for ML models, Cryptographic BOM for PQC migration, SaaSBOM, Hardware BOM, Operations BOM).
Table of Contents
SBOM Standards and Formats
SPDX SBOM Format Deep Dive
CycloneDX SBOM Format Deep Dive
NTIA Minimum Elements Compliance
Package URL (purl) for Component Identification
SBOM Generation Tools
Syft for Container Image SBOM Generation
Trivy SBOM Generation and Scanning
cdxgen for Application Level SBOMs
Docker Scout SBOM and Analysis
Microsoft SBOM Tool (sbom-tool)
GitHub Dependency Graph and SBOM Export
Cloud SBOM Solutions
Amazon Inspector SBOM for ECR Container Images
Amazon Inspector SBOM for Lambda Functions
Amazon Inspector SBOM for EC2 Instances
Azure Defender for Containers SBOM
GCP Artifact Analysis and On-Demand Scanning
CI/CD SBOM Pipelines
GitHub Actions SBOM Pipeline
GitLab CI SBOM Pipeline
SBOM Attestation with Cosign
Kyverno SBOM Admission Policy
SBOM Consumption and Analysis
OWASP Dependency-Track for SBOM Management
GUAC (Graph for Understanding Artifact Composition)
Grype SBOM-Based Vulnerability Scanning
Vulnerability Triage with VEX
OpenVEX for Vulnerability Exploitability
CycloneDX VEX (Vulnerability Disclosure Report)
CSAF (Common Security Advisory Framework)
Alternative Bill of Materials Types
AI Bill of Materials (AI BOM / ML BOM)
Cryptographic Bill of Materials (CBOM)
SaaSBOM (Software-as-a-Service Bill of Materials)
Hardware Bill of Materials (HBOM)
Operations Bill of Materials (OBOM)
Advanced SBOM Topics
SBOM Diff and Drift Detection
SBOM for Kubernetes Clusters
SBOM for License Compliance
DefectDojo SBOM Ingestion and Vulnerability Management
Complete SBOM Pipeline End-to-End
Lab 01: SPDX SBOM Format Deep Dive
SPDX (Software Package Data Exchange) is a Linux Foundation project and an ISO standard for communicating software bill of materials information. It was the first SBOM format to gain widespread adoption and remains the format specified in many government procurement requirements.
We focus on SPDX 2.3 in this lab because it is the current stable release and the version most tools generate by default. SPDX 3.0 exists but tooling support is still catching up.
SPDX documents have five main element types:
Document: the top-level container with creation info, document namespace, and licensing metadata
Package: a unit of software (library, application, container layer, OS package)
File: an individual file within a package, with checksums and license info
Snippet: a portion of a file (rarely used in practice)
Relationship: how elements relate to each other (DEPENDS_ON, CONTAINS, BUILD_TOOL_OF, DESCRIBED_BY, and others)
SPDX supports multiple serialization formats: SPDX-JSON, SPDX-TV (tag-value), SPDX-RDF, SPDX-YAML, and SPDX-XML. JSON is the most common in CI/CD pipelines today.
Required fields at the document level:
SPDXVersion(e.g., SPDX-2.3)DataLicense(always CC0-1.0, this licenses the SBOM data itself)SPDXID(SPDXRef-DOCUMENT)DocumentNameDocumentNamespace(a unique URI for this specific SBOM)
Package-level fields that matter for vulnerability matching:
name,versionInfo,downloadLocationsupplier(who distributes the package)externalRefswith typecpe23Typeorpurlfor cross-referencing vulnerability databases
Root Cause Analysis
Organizations fall into two failure modes with SPDX. The first is having no SBOM at all, meaning they ship software with zero visibility into its composition. The second, more subtle failure, is generating malformed SPDX documents that are missing required fields. A malformed SBOM gives a false sense of compliance. Downstream consumers (vulnerability scanners, procurement systems, compliance auditors) silently fail when fields like supplier or externalRefs are absent.
The root cause is typically that teams add SBOM generation as an afterthought. They run a quick syft or trivy command, store the output, and never validate it.
Vulnerable Configuration
A CI pipeline that generates no SBOM, or generates a broken one:
# .github/workflows/build.yml - VULNERABLE
name: Build and Push
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:latest .
- name: Push image
run: docker push myapp:latest
# No SBOM generation at all
Or worse, a hand-crafted SPDX file missing required fields:
{
"spdxVersion": "SPDX-2.3",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "myapp",
"packages": [
{
"name": "express",
"SPDXID": "SPDXRef-Package-express"
}
]
}
This document is missing dataLicense, documentNamespace, creationInfo, package versionInfo, downloadLocation, supplier, and externalRefs. It will fail validation and is useless for vulnerability matching.
Exploitation
Step 1: Show the problem with missing SBOMs
Pull a container image and inspect it without SBOM tooling:
docker pull nginx:1.25
docker inspect nginx:1.25 | jq '.[0].RootFS.Layers | length'
Expected output:
7
We see 7 layers but have zero information about the hundreds of packages inside those layers.
Step 2: Generate a proper SPDX SBOM with Syft
syft nginx:1.25 -o spdx-json=nginx-spdx.json
Expected output:
✔ Pulled image
✔ Loaded image nginx:1.25
✔ Parsed image sha256:a8758716bb6a...
✔ Cataloged contents 156 packages
├── dpkg 90 packages
├── java-archive 0 packages
└── ...
Step 3: Examine the generated SPDX structure
jq '{
spdxVersion: .spdxVersion,
dataLicense: .dataLicense,
SPDXID: .SPDXID,
name: .name,
documentNamespace: .documentNamespace,
packageCount: (.packages | length),
relationshipCount: (.relationships | length)
}' nginx-spdx.json
Expected output:
{
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "nginx-1.25",
"documentNamespace": "https://anchore.com/syft/image/nginx-1.25-...",
"packageCount": 156,
"relationshipCount": 312
}
Step 4: Check a single package for required fields
jq '.packages[] | select(.name == "openssl") | {
name, versionInfo, downloadLocation, supplier,
externalRefs: [.externalRefs[]? | {referenceType, referenceLocator}]
}' nginx-spdx.json
Expected output:
{
"name": "openssl",
"versionInfo": "3.0.11-1~deb12u2",
"downloadLocation": "NOASSERTION",
"supplier": "Organization: Debian",
"externalRefs": [
{
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:a:openssl:openssl:3.0.11-1~deb12u2:*:*:*:*:*:*:*"
},
{
"referenceType": "purl",
"referenceLocator": "pkg:deb/debian/openssl@3.0.11-1~deb12u2?arch=amd64&distro=debian-12"
}
]
}
Step 5: Validate the SPDX document
pip install spdx-tools
python -m spdx_tools.spdx.parser.parse nginx-spdx.json
Expected output for a valid document:
The document is valid.
For an invalid document with missing fields:
Validation errors:
- document_namespace is required
- data_license must be CC0-1.0
- creation_info.created is missing
Step 6: Check NTIA minimum elements compliance
pip install ntia-conformance-checker
ntia-checker --file nginx-spdx.json
Expected output:
NTIA Conformance Results:
Supplier Name: PASS
Component Name: PASS
Version: PASS
Unique Identifier: PASS
Dependency Relationship: PASS
Author of SBOM Data: PASS
Timestamp: PASS
Overall: COMPLIANT
Detection
Detect missing or invalid SBOMs in your environment:
# Check if an image has an attached SBOM (cosign)
cosign verify-attestation --type spdx myregistry.io/myapp:v1.2.3
# Validate SPDX documents in a directory
for f in sboms/*.json; do
echo "Validating $f..."
python -m spdx_tools.spdx.parser.parse "$f" 2>&1 | tail -1
done
# Count packages missing purl references
jq '[.packages[] | select(.externalRefs == null or
(.externalRefs | map(.referenceType) | contains(["purl"]) | not))] |
length' sbom.json
Solution
Fixed CI pipeline with SPDX generation, validation, and attestation:
# .github/workflows/build.yml - FIXED
name: Build and Push with SBOM
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install SBOM tools
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
pip install spdx-tools ntia-conformance-checker
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Generate SPDX SBOM
run: |
syft myapp:${{ github.sha }} -o spdx-json=sbom-spdx.json
- name: Validate SPDX
run: |
python -m spdx_tools.spdx.parser.parse sbom-spdx.json
- name: Check NTIA compliance
run: |
ntia-checker --file sbom-spdx.json --output ntia-report.json
# Fail if not compliant
ntia-checker --file sbom-spdx.json | grep -q "COMPLIANT"
- name: Attest SBOM to image
run: |
cosign attest --predicate sbom-spdx.json \
--type spdx \
myregistry.io/myapp:${{ github.sha }}
- name: Upload SBOM artifact
uses: actions/upload-artifact@v4
with:
name: sbom-spdx
path: sbom-spdx.json
Verification
After applying the fix, verify the SBOM is valid and attached:
# 1. Validate the generated SPDX
python -m spdx_tools.spdx.parser.parse sbom-spdx.json
# Expected: "The document is valid."
# 2. Confirm all required SPDX fields exist
jq '{
hasVersion: (.spdxVersion != null),
hasLicense: (.dataLicense == "CC0-1.0"),
hasNamespace: (.documentNamespace != null),
hasCreationInfo: (.creationInfo != null),
packageCount: (.packages | length)
}' sbom-spdx.json
# 3. Check NTIA compliance
ntia-checker --file sbom-spdx.json
# Expected: Overall: COMPLIANT
# 4. Verify the attestation on the image
cosign verify-attestation --type spdx \
--certificate-identity-regexp ".*" \
--certificate-oidc-issuer-regexp ".*" \
myregistry.io/myapp:latest



