Key takeaways
- An SBOM answers in seconds the question that cost teams weeks during Log4Shell: "do we even ship this component?".
syft image:my-app:latest -o cyclonedx-json=sbom.cdx.jsongenerates one on every build. Format choice: CycloneDX (OWASP, security-oriented, carries VEX natively) for operations, SPDX (ISO 5962:2021, Linux Foundation) for license compliance — Syft emits both. - On the SCA side, the three scanners do not overlap: Trivy covers images, filesystems, Git repos, IaC and secrets — the CI Swiss army knife; Grype targets maximum precision on dependencies and reads an SBOM directly (
grype sbom:sbom.cdx.json); OWASP Dependency-Check does the deep source-code audit but stays slow (NVD API). The command that actually blocks a pipeline:trivy image --severity HIGH,CRITICAL --exit-code 1 my-app:latest. - Three non-negotiable hardening steps for GitHub Actions: pin every action by SHA hash, not by tag (
actions/checkout@b4ffde65...), because av4tag can be repointed to a malicious commit —pinact runautomates the conversion; setpermissions: contents: readas the default and only grantpackages: writeorid-token: writeto the job that needs it; never interpolate${{ github.event.pull_request.title }}inside arun:block — pass it through an environment variable, otherwise command injection is trivial. - Cosign keyless mode removes the private-key storage problem entirely: the identity comes from the CI job's OIDC token. Signing is one line (
cosign sign --yes $IMAGE@$DIGEST), and verification on the consumer side checks the workflow identity withcosign verify --certificate-identity "https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main" --certificate-oidc-issuer "https://token.actions.githubusercontent.com". - The SLSA framework puts numbers on the effort: level 1 (scripted build, unsigned provenance) takes a few hours, level 2 (hosted build service, signed provenance via
slsa-github-generator) 1-2 days, level 3 (strong build isolation) weeks, level 4 (two-party review) months. Most projects stop at level 2-3, the best security-to-effort ratio.
SolarWinds (2020), Codecov (2021), Log4Shell (2021), XZ Utils (2024): attacks targeting the software supply chain have become the most devastating attack vector. A single compromised component can affect thousands of organizations simultaneously.
Why the software supply chain is the priority target
In December 2020, attackers compromised the build systems of SolarWinds and inserted a backdoor into legitimate updates of the Orion suite. More than 18,000 organizations downloaded the poisoned update, including US government agencies. The attack went undetected for 9 months.
In December 2021, CVE-2021-44228 (Log4Shell) revealed a critical vulnerability in Log4j, a Java library used in hundreds of thousands of applications. The problem: most teams did not even know they were using Log4j — it was present as a transitive dependency, hidden several levels deep.
These two incidents illustrate the two main vectors of supply chain attacks:
- Build chain compromise: an attacker tampers with the compilation process or the build tools
- Vulnerabilities in dependencies: third-party libraries contain unknown or deliberately introduced flaws
This tutorial covers the four defensive pillars: inventory (SBOM), detection (SCA), pipeline hardening (CI/CD) and artifact verification (signing + provenance).
SBOM: Software Bill of Materials
An SBOM is the software equivalent of a food product's ingredient list. It exhaustively lists all the components of an application: direct libraries, transitive dependencies, exact versions, licenses and relationships between components.
CycloneDX vs SPDX: choosing your format
Two standards dominate the market:
- SPDX (Software Package Data Exchange) — ISO 5962:2021 standard, maintained by the Linux Foundation. License-compliance oriented. XML, JSON or RDF format. Recommended for legal analysis and open source distribution.
- CycloneDX — OWASP standard, security oriented. Supports SBOMs, HBOMs (hardware), MBOMs (ML models) and VEX documents. JSON, XML or Protocol Buffers format. Recommended for DevSecOps use cases.
Use CycloneDX for operational security (CVE tracking, VEX) and SPDX if your legal team needs license compliance analysis. Both formats are interoperable and Syft generates both.
Generating an SBOM with Syft
Syft (Anchore) is the reference tool for generating SBOMs from Docker images, source code directories or archives.
# Installing Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Check the installation
syft version
# Generate an SBOM from a Docker image (CycloneDX JSON format)
syft image:my-app:latest -o cyclonedx-json=sbom.cdx.json
# Generate an SBOM from an image with a full tag
syft registry:myregistry.io/my-app:v1.2.3 -o cyclonedx-json=sbom.cdx.json
# Generate from a directory (analyzes the source code)
syft dir:/path/to/project -o cyclonedx-json=sbom.cdx.json
# SPDX JSON format
syft image:my-app:latest -o spdx-json=sbom.spdx.json
# SPDX tag-value format (for legal toolchains)
syft image:my-app:latest -o spdx-tag-value=sbom.spdx
# Table display for quick inspection
syft image:my-app:latest -o table
# Include dev packages in the SBOM
syft dir:/project -o cyclonedx-json --scope all-layers
A CycloneDX JSON SBOM looks like this (simplified excerpt):
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79",
"version": 1,
"metadata": {
"timestamp": "2026-02-20T10:00:00Z",
"tools": [{"vendor": "anchore", "name": "syft", "version": "1.4.0"}],
"component": {
"type": "container",
"name": "my-app",
"version": "1.2.3"
}
},
"components": [
{
"type": "library",
"name": "log4j-core",
"version": "2.17.1",
"purl": "pkg:maven/org.apache.logging.log4j/[email protected]",
"licenses": [{"expression": "Apache-2.0"}]
}
]
}
Integrating the SBOM into your CI pipeline
The SBOM must be generated at every build and archived as an artifact. Here is a GitHub Actions example:
# .github/workflows/sbom.yml
name: SBOM Generation
on:
push:
branches: [main]
release:
types: [published]
jobs:
sbom:
runs-on: ubuntu-latest
permissions:
contents: write
packages: read
steps:
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Build image
run: docker build -t my-app:${{ github.sha }} .
- name: Install Syft
uses: anchore/sbom-action/download-syft@fd74a6fb98a204a1ad35bbfae0122c1a302ff88d # v0.15.0
- name: Generate SBOM
uses: anchore/sbom-action@fd74a6fb98a204a1ad35bbfae0122c1a302ff88d # v0.15.0
with:
image: my-app:${{ github.sha }}
format: cyclonedx-json
output-file: sbom-${{ github.sha }}.cdx.json
artifact-name: sbom-${{ github.sha }}.cdx.json
- name: Upload SBOM as release asset
if: github.event_name == 'release'
uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 # v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: sbom-${{ github.sha }}.cdx.json
asset_name: sbom.cdx.json
asset_content_type: application/json
Premium Content
This advanced tutorial is reserved for premium members.
- All advanced tutorials
- New content every week
- Progress tracking
- Cancel anytime