Introduction
Software supply chain security has become critical in 2026, following major incidents like Log4Shell and SolarWinds attacks. It protects the entire software lifecycle, from open-source dependencies to deployed artifacts. A breach in the supply chain can compromise millions of users, with costs estimated in billions.
This advanced tutorial guides you step-by-step to implement defense-in-depth: static and dynamic vulnerability scans (SCA/SBOM), cryptographic container signing (Cosign/Sigstore), SLSA frameworks for pipeline integrity, and continuous monitoring with Dependency-Track. We use mature open-source tools like Trivy, Cosign, and GitHub Actions.
By the end, you'll have a CI/CD pipeline attesting SLSA Level 2, protecting against malicious injections and upstream compromises. Ideal for DevSecOps teams seeking a production-ready implementation (128 words).
Prerequisites
- Docker and Docker Compose installed (version 27+).
- GitHub account with a private repository.
- Cosign tool installed via
brew install cosign(macOS) or equivalent. - Node.js 20+ and npm/yarn for a sample project.
- Access to a container registry (Docker Hub or GHCR).
- Advanced knowledge of CI/CD and cryptography.
Install Trivy and Scan Dependencies
#!/bin/bash
# Install Trivy (open-source vulnerability scanner)
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.55.0
# Create a sample Node.js project
mkdir supply-chain-demo && cd supply-chain-demo
npm init -y
npm install lodash express
# Generate SBOM and scan
trivy fs . --format json --output trivy-deps.json --scanners vuln,license,malware
trivy sbom trivy-deps.json --format table
# Example output if critical vulnerabilities found
# Display summary
trivy fs . --format sarif --output results.sarifThis script installs Trivy, creates a vulnerable Node.js project, generates an SBOM, and scans dependencies for vulnerabilities, licenses, and malware. It produces a GitHub-compatible SARIF report. Pitfall: Always scan in vuln,license mode for a complete view; don't ignore false positives without manual triage.
Interpreting Trivy Scans
Trivy detects CVEs in dependencies like lodash (e.g., CVE-2021-23337). The SBOM (Software Bill of Materials) lists all components, essential for traceability. Integrate SARIF reports into GitHub for automatic alerts.
Analogy: Think of your supply chain like a food supply chain; Trivy is the health inspection spotting contaminants before distribution.
Set Up GitHub Actions for Automatic SCA
name: SCA with Trivy
on: [push, pull_request]
permissions:
contents: read
id-token: write
security-events: write
jobs:
sca:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'This GitHub Actions workflow automatically scans on every push/PR with Trivy and uploads SARIF results for security alerts. Minimal permissions (id-token: write) for SLSA compliance. Pitfall: Limit severity to avoid blocking false positives; test on branches before main.
Secure Dockerfile with Minimal Base Image
FROM node:20-alpine AS base
# Install deps without cache for reproducibility
WORKDIR /app
COPY package*.json .
RUN --mount=type=cache,target=/root/.npm npm ci --only=production --no-optional
# Copy source
COPY . .
# Multi-stage for minimal runtime
FROM base AS runner
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
# SLSA labels
LABEL org.opencontainers.image.source=https://github.com/user/supply-chain-demo
LABEL org.opencontainers.image.licenses=MITMulti-stage Dockerfile using Node Alpine, non-root user, npm cache, and OCI labels for traceability. Avoids unnecessary layers to reduce attack surface. Pitfall: Use --mount=type=cache in CI to speed up builds without compromising reproducibility.
Build and Push the Image
Build with docker build -t ghcr.io/user/supply-chain-demo:v1 . and push to GHCR. The labels enable downstream verification.
Sign the Image with Cosign
#!/bin/bash
# Authenticate to GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
# Build and tag
IMAGE=ghcr.io/USERNAME/supply-chain-demo:v1
docker build -t $IMAGE .
docker push $IMAGE
# Generate Cosign key pair (or use GitHub OIDC)
cosign generate-key-pair
# Sign with private key
cosign sign -key cosign.key $IMAGE
# SLSA provenance attestation
cosign attest --predicate-type https://slsa.dev/provenance/v1 $IMAGE
# Verify
cosign verify $IMAGEComplete script to build, push, sign, and attest an image with Cosign using GitHub OIDC (implicit token). Creates Fulcio/Rekor signatures for keyless verification. Pitfall: Use OIDC (permissions: id-token) in CI to avoid static secrets.
GitHub Actions Workflow for SLSA Level 2 Build/Sign
name: Build and Sign SLSA L2
on: [push]
permissions:
contents: read
id-token: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/project-everest/kstool:slsa2-debian11
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: slsa-framework/slsa-github-generator/.github/actions/slsa-build-provenance@main
with:
provenance-name: 'supply-chain-demo'
base64-provenance: false
- name: Sign
run: |
cosign sign --yes ghcr.io/USERNAME/supply-chain-demo@${{ steps.slsa.outputs.artifact-digest }}SLSA Level 2 workflow using kstool and slsa-github-generator for reproducible container builds and provenance attestations. Cosign signs immutable digest. Pitfall: fetch-depth: 0 for full Git history; validate SLSA compliance with slsa-verifier.
Deploy Dependency-Track for SBOM Monitoring
version: '3.8'
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: dtrack
POSTGRES_USER: dtrack
POSTGRES_PASSWORD: changeit
volumes:
- postgres:/var/lib/postgresql/data
dependency-track:
image: owasp/dependency-track:4.14.0
ports:
- "8081:8080"
environment:
ALPINE_CDN_MIRROR: http://dl-cdn.alpinelinux.org/alpine
depends_on:
- postgres
volumes:
- dtrack:/data
volumes:
postgres:
dtrack:
# Upload SBOM: curl -X POST http://localhost:8081/api/v1/bom -F autoCreate=true -F projectUuid=UUID -F bom=@trivy-deps.jsonDocker Compose setup for Dependency-Track, an SBOM monitoring tool that tracks vulnerabilities continuously. Upload SBOMs via API. Pitfall: Change default credentials; integrate into CI for automated uploads with a fixed projectUuid.
Best Practices
- SLSA at minimum Level 2: Ensure non-falsifiable build provenance.
- Systematic SBOMs: Generate in CycloneDX/SPDX for all artifacts.
- Hierarchical signatures: Cosign + Rekor for chain of trust.
- Zero-trust pipelines: OIDC permissions, no long-lived secrets.
- Proactive alerting: Integrate SARIF with GitHub Advanced Security.
Common Mistakes to Avoid
- Scanning only in dev: Always in CI/CD and pre-prod to catch upstream issues.
- Ignoring signatures: Always verify with
cosign verify --certificate-identity. - Unpinned dependencies: Use lockfiles (package-lock.json) for reproducibility.
- Root builds: Non-root users and multi-stage to reduce blast radius.
Next Steps
- SLSA documentation: slsa.dev.
- Sigstore: sigstore.dev.
- Advanced DevSecOps training: Learni Trainings.
- Tools: in-toto, Chainguard Images.
- Book: 'Secure Software Supply Chain' by Dan Lorenc.