Module 4.4: Supply Chain Security
Discipline Module | Complexity:
[COMPLEX]| Time: 40-45 min
Prerequisites
Section titled “Prerequisites”Before starting this module:
- Required: Module 4.3: Security in CI/CD — Pipeline security
- Required: Understanding of container registries and image distribution
- Recommended: Basic cryptography concepts (signing, verification)
- Helpful: Experience with package managers (npm, pip, go modules)
What You’ll Be Able to Do
Section titled “What You’ll Be Able to Do”After completing this module, you will be able to:
- Implement software supply chain security using SLSA framework, Sigstore signing, and SBOM generation
- Design artifact verification pipelines that validate image provenance and integrity before deployment
- Build admission controllers that enforce signed image policies on Kubernetes clusters
- Evaluate supply chain attack vectors — dependency confusion, typosquatting, compromised builds — and implement countermeasures
Why This Module Matters
Section titled “Why This Module Matters”You’ve locked down your code. Your pipeline has security gates. Your containers are scanned.
But where did that base image come from?
Who maintains that npm package with 50 million weekly downloads?
Can you prove your production binary matches your source code?
Supply chain attacks target the weakest link—not your code, but everything around it. The 2020 SolarWinds attack compromised 18,000 organizations through one malicious update. The 2021 Codecov breach exposed secrets from 29,000 repositories.
After this module, you’ll understand:
- How software supply chain attacks work
- SBOM (Software Bill of Materials) generation and use
- Image signing and verification with Sigstore
- SLSA framework for supply chain integrity
- Practical defenses at each layer
For a ready-to-use checklist you can apply to any project today, see the Supply Chain Defense Guide.
Understanding the Software Supply Chain
Section titled “Understanding the Software Supply Chain”The Attack Surface
Section titled “The Attack Surface”┌─────────────────────────────────────────────────────────────────┐│ SOFTWARE SUPPLY CHAIN ATTACK SURFACE ││ ││ SOURCE DEPENDENCIES BUILD ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ Your │ │ Direct │ │ Build │ ││ │ code │◀─────────│ deps │◀─────────│ process │ ││ │ │ │ │ │ │ ││ │ Attack: │ │ Attack: │ │ Attack: │ ││ │ Insider │ │ Typosquat│ │ Inject │ ││ │ Commit │ │ Hijack │ │ malware │ ││ └─────────┘ └────┬────┘ └─────────┘ ││ │ ││ ┌────▼────┐ ││ │Transitive│ ││ │ deps │ ││ │ │ ││ │ Attack: │ ││ │ Hidden │ ││ │ in tree │ ││ └─────────┘ ││ ││ ARTIFACTS DISTRIBUTION RUNTIME ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ Images │─────────▶│ Registry│─────────▶│ Cluster │ ││ │ Binaries│ │ CDN │ │ Servers │ ││ │ │ │ │ │ │ ││ │ Attack: │ │ Attack: │ │ Attack: │ ││ │ Tamper │ │ MITM │ │ Replace │ ││ │ before │ │ Poison │ │ at │ ││ │ sign │ │ cache │ │ deploy │ ││ └─────────┘ └─────────┘ └─────────┘ ││ │└─────────────────────────────────────────────────────────────────┘Real Supply Chain Attacks
Section titled “Real Supply Chain Attacks”| Attack | Year | Impact | Vector |
|---|---|---|---|
| Trivy/LiteLLM | 2026 | 3.4M daily downloads, K8s clusters backdoored | CI tool compromise + credential theft |
| SolarWinds | 2020 | 18,000 orgs, including US gov | Build process injection |
| Codecov | 2021 | 29,000 repos exposed | CI script tampering |
| Log4Shell | 2021 | Millions of apps | Transitive dependency |
| ua-parser-js | 2021 | 8M weekly downloads | Maintainer compromise |
| event-stream | 2018 | 2M weekly downloads | New maintainer attack |
| PyPI typosquatting | Ongoing | Thousands of installs | Package name confusion |
Software Bill of Materials (SBOM)
Section titled “Software Bill of Materials (SBOM)”What is an SBOM?
Section titled “What is an SBOM?”An SBOM is a complete inventory of components in your software:
┌─────────────────────────────────────────────────────────────┐│ SBOM ││ ││ Application: my-web-app v1.2.3 ││ Build Date: 2024-01-15 ││ Build Tool: docker 24.0.7 ││ ││ ┌─────────────────────────────────────────────────────────┐││ │ COMPONENTS │││ ├─────────────────────────────────────────────────────────┤││ │ pkg:npm/express@4.18.2 │││ │ pkg:npm/lodash@4.17.21 │││ │ pkg:npm/axios@1.6.0 │││ │ pkg:apk/alpine-baselayout@3.4.3 │││ │ pkg:apk/openssl@3.1.4 │││ │ ... (hundreds more) │││ └─────────────────────────────────────────────────────────┘││ ││ RELATIONSHIPS ││ express@4.18.2 ──depends-on──▶ body-parser@1.20.2 ││ body-parser@1.20.2 ──depends-on──▶ bytes@3.1.2 ││ │└─────────────────────────────────────────────────────────────┘Why SBOMs Matter
Section titled “Why SBOMs Matter”Without SBOM (Log4Shell scenario):
Security Team: "Are we affected by Log4Shell?"Developer 1: "I don't think we use Log4j..."Developer 2: "Let me grep the codebase..."Developer 3: "What about that Java service?"[3 weeks later]Developer 1: "Found it in a transitive dependency!"With SBOM:
$ grype sbom:./my-app.sbom --only-vuln-id CVE-2021-44228NAME INSTALLED FIXED-IN VULNERABILITYlog4j 2.14.1 2.17.1 CVE-2021-44228 CriticalTime to answer: 3 seconds vs 3 weeks.
SBOM Formats
Section titled “SBOM Formats”| Format | Origin | Best For |
|---|---|---|
| SPDX | Linux Foundation | License compliance, legal |
| CycloneDX | OWASP | Security, vulnerability tracking |
| SWID | ISO | Enterprise asset management |
Generating SBOMs
Section titled “Generating SBOMs”Syft (by Anchore):
# Generate SBOM from container imagesyft myapp:latest -o spdx-json > sbom.spdx.json
# Generate SBOM from directorysyft dir:./src -o cyclonedx-json > sbom.cdx.jsonTrivy:
# Generate SBOM from container imagetrivy image --format spdx-json myapp:latest > sbom.spdx.json
# Generate SBOM from filesystemtrivy fs --format cyclonedx ./src > sbom.cdx.jsonIn Docker Build:
# Docker BuildKit native SBOMdocker buildx build --sbom=true -t myapp:latest .
# Attach SBOM as attestationdocker buildx build --attest type=sbom -t myapp:latest .Scanning SBOMs for Vulnerabilities
Section titled “Scanning SBOMs for Vulnerabilities”# Grype (Anchore)grype sbom:./sbom.cdx.json
# Trivytrivy sbom ./sbom.spdx.json
# OutputNAME VERSION VULNERABILITY SEVERITYlodash 4.17.20 CVE-2021-23337 HIGHaxios 0.21.0 CVE-2021-3749 MEDIUMDid You Know?
Section titled “Did You Know?”-
US Executive Order 14028 (May 2021) requires SBOMs for all software sold to the federal government. This single order catalyzed the entire industry’s SBOM adoption.
-
The average enterprise application contains 528 open source components, but most organizations can only account for about 10% of them without automated SBOM generation.
-
Sigstore was founded in 2020 by Google, Red Hat, and Purdue University. It provides free code signing for open source—and has already signed over 20 million artifacts.
-
The SLSA framework name is pronounced “salsa” and stands for Supply chain Levels for Software Artifacts. It was created by Google based on their internal Binary Authorization system that secures their entire production environment.
Image Signing with Sigstore
Section titled “Image Signing with Sigstore”Why Sign Images?
Section titled “Why Sign Images?”┌─────────────────────────────────────────────────────────────┐│ WITHOUT SIGNING ││ ││ CI/CD ──build──▶ Registry ◀──pull── Cluster ││ │ ││ Trust the ││ registry? ││ What if ││ compromised? ││ │├─────────────────────────────────────────────────────────────┤│ WITH SIGNING ││ ││ CI/CD ──build──▶ Sign ──▶ Registry ◀──pull── Verify ──▶ K8s││ │ │ ││ Private key Verify signature ││ (secure) matches trusted key ││ ││ Even if registry compromised, unsigned images rejected ││ │└─────────────────────────────────────────────────────────────┘The Sigstore Ecosystem
Section titled “The Sigstore Ecosystem”┌─────────────────────────────────────────────────────────────┐│ SIGSTORE ECOSYSTEM ││ ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ││ │ Cosign │ │ Fulcio │ │ Rekor │ ││ │ │ │ │ │ │ ││ │ Sign & │───▶│ Issue │───▶│ Record │ ││ │ verify │ │ certs │ │ in log │ ││ │ images │ │ (OIDC) │ │ (immut.) │ ││ └──────────┘ └──────────┘ └──────────┘ ││ │ ││ │ ┌──────────────────────────────────────────┐ ││ └───▶│ No long-lived keys to manage! │ ││ │ Sign with your identity (GitHub, Google) │ ││ │ Transparent log proves when signed │ ││ └──────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Signing with Cosign
Section titled “Signing with Cosign”Installation:
# macOSbrew install cosign
# Linuxcurl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64chmod +x cosign-linux-amd64sudo mv cosign-linux-amd64 /usr/local/bin/cosignKeyless signing (recommended):
# Sign image (opens browser for OIDC auth)cosign sign ghcr.io/myorg/myapp:v1.0.0
# This will:# 1. Authenticate you via OIDC (GitHub, Google, etc.)# 2. Get ephemeral certificate from Fulcio# 3. Sign the image# 4. Record signature in Rekor transparency logVerify signature:
# Verify image was signed by specific identitycosign verify ghcr.io/myorg/myapp:v1.0.0 \ --certificate-identity developer@example.com \ --certificate-oidc-issuer https://accounts.google.com
# Verify image was signed by GitHub Actions in specific repocosign verify ghcr.io/myorg/myapp:v1.0.0 \ --certificate-identity-regexp 'https://github.com/myorg/myapp/.github/workflows/.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.comGitHub Actions: Sign Images
Section titled “GitHub Actions: Sign Images”name: Build, Sign, Pushon: push: tags: ['v*']
jobs: build-and-sign: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write # Required for keyless signing
steps: - uses: actions/checkout@v4
- name: Install Cosign uses: sigstore/cosign-installer@v3
- name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push id: build uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
- name: Sign image env: COSIGN_EXPERIMENTAL: "true" run: | cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}Kubernetes Admission Control
Section titled “Kubernetes Admission Control”Kyverno policy to require signed images:
apiVersion: kyverno.io/v1kind: ClusterPolicymetadata: name: require-signed-imagesspec: validationFailureAction: Enforce rules: - name: verify-signature match: any: - resources: kinds: - Pod verifyImages: - imageReferences: - "ghcr.io/myorg/*" attestors: - entries: - keyless: subject: "https://github.com/myorg/*" issuer: "https://token.actions.githubusercontent.com"Sigstore Policy Controller:
apiVersion: policy.sigstore.dev/v1alpha1kind: ClusterImagePolicymetadata: name: signed-images-policyspec: images: - glob: "ghcr.io/myorg/**" authorities: - keyless: identities: - issuerRegExp: "https://token.actions.githubusercontent.com" subjectRegExp: "https://github.com/myorg/.*"The SLSA Framework
Section titled “The SLSA Framework”What is SLSA?
Section titled “What is SLSA?”SLSA (Supply chain Levels for Software Artifacts) is a framework for achieving supply chain integrity.
┌─────────────────────────────────────────────────────────────┐│ SLSA LEVELS ││ ││ Level 0: No guarantees ││ ───────────────────── ││ No provenance, no verification ││ ││ Level 1: Provenance ││ ─────────────────── ││ Build process documented ││ Provenance generated automatically ││ ││ Level 2: Tamper Resistant ││ ───────────────────────── ││ Hosted build service ││ Authenticated provenance ││ ││ Level 3: Hardened Builds ││ ──────────────────────── ││ Isolated, ephemeral build environment ││ Non-falsifiable provenance ││ ││ Level 4: (Future) ││ Two-person review, hermetic builds ││ │└─────────────────────────────────────────────────────────────┘SLSA Requirements by Level
Section titled “SLSA Requirements by Level”| Requirement | L1 | L2 | L3 |
|---|---|---|---|
| Scripted build | ✓ | ✓ | ✓ |
| Build service | ✓ | ✓ | |
| Provenance generated | ✓ | ✓ | ✓ |
| Authenticated provenance | ✓ | ✓ | |
| Isolated build | ✓ | ||
| Ephemeral environment | ✓ | ||
| Non-falsifiable provenance | ✓ |
Generating SLSA Provenance
Section titled “Generating SLSA Provenance”GitHub Actions with SLSA Generator:
name: SLSA Buildon: push: tags: ['v*']
jobs: build: runs-on: ubuntu-latest outputs: digest: ${{ steps.build.outputs.digest }} steps: - uses: actions/checkout@v4 - name: Build image id: build uses: docker/build-push-action@v5 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
provenance: needs: build permissions: actions: read id-token: write packages: write uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0 with: image: ghcr.io/${{ github.repository }} digest: ${{ needs.build.outputs.digest }} registry-username: ${{ github.actor }} secrets: registry-password: ${{ secrets.GITHUB_TOKEN }}Verifying SLSA Provenance
Section titled “Verifying SLSA Provenance”# Install SLSA verifiergo install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
# Verify container image provenanceslsa-verifier verify-image ghcr.io/myorg/myapp@sha256:abc123 \ --source-uri github.com/myorg/myapp \ --source-tag v1.0.0
# Verify artifact provenanceslsa-verifier verify-artifact myapp-linux-amd64 \ --provenance-path myapp-linux-amd64.intoto.jsonl \ --source-uri github.com/myorg/myappWar Story: When the Security Scanner Became the Weapon (March 2026)
Section titled “War Story: When the Security Scanner Became the Weapon (March 2026)”On March 24, 2026, the LiteLLM project — an AI model routing library with 3.4 million daily PyPI downloads — was backdoored. The attack chain started five days earlier with a compromise that nobody expected: Trivy, the most trusted open source security scanner in the Kubernetes ecosystem.
The Attack Chain:
DAY 1: Attacker (TeamPCP) rewrites Git tags in trivy-action GitHub Action └─▶ Tag v0.69.4 now points to malicious release
DAY 5: LiteLLM CI/CD runs trivy-action WITHOUT a pinned version └─▶ Pulls compromised Trivy └─▶ Malicious scanner exfiltrates PYPI_PUBLISH token └─▶ Attacker publishes litellm 1.82.7 and 1.82.8 └─▶ 3.4M daily downloads, live for 3 hoursTwo Delivery Mechanisms:
Version 1.82.7 embedded a Base64-encoded payload in litellm/proxy/proxy_server.py that executed on import. Version 1.82.8 was worse — it dropped a .pth file into site-packages that executed on every Python interpreter startup, including pip, python -c, and IDE language servers.
Three-Stage Payload:
Stage 1 harvested SSH keys, AWS/GCP/Azure credentials (with IMDSv2 signing), Docker registry credentials, Kubernetes kubeconfig files, service account tokens, and cryptocurrency wallets. Stage 2 encrypted everything with AES-256 and exfiltrated to models.litellm.cloud. Stage 3 deployed persistent backdoor pods named node-setup-* into the kube-system namespace with host filesystem mounts, installing backdoors on the underlying nodes.
# What the backdoor deployed into victim clustersapiVersion: v1kind: Podmetadata: name: node-setup-worker-1 # Looks legitimate namespace: kube-system # Hides among system podsspec: hostPID: true hostNetwork: true containers: - name: setup securityContext: privileged: true # Full host access volumeMounts: - name: host-root mountPath: /host # Mounts entire host filesystem volumes: - name: host-root hostPath: path: /The Root Cause:
One line in LiteLLM’s CI/CD configuration:
# WHAT LITELLM HAD (vulnerable):- uses: aquasecurity/trivy-action@latest
# WHAT THEY SHOULD HAVE HAD (pinned to commit SHA):- uses: aquasecurity/trivy-action@a7a829a0ece790ca07e16ed53ba6daba6e7e4e04Git tags are mutable. Anyone with write access can rewrite where a tag points. Commit SHAs are immutable.
The PYPI_PUBLISH token was also accessible to every step in the workflow, including the security scanner. It should have been scoped to a dedicated publish job with permissions: restricted.
Detection:
A security researcher at FutureSearch was testing a Cursor MCP plugin when his machine started thrashing. He traced the RAM exhaustion to litellm’s .pth file causing a fork bomb. Within an hour, the disclosure hit Reddit and Hacker News. PyPI quarantined the packages three hours after publication.
Postmortem: Five Failures
| # | Failure | Defense |
|---|---|---|
| 1 | Unpinned GitHub Action tag | Pin all actions to commit SHA, never @latest or @v1 |
| 2 | Publish token accessible to scanner | Scope secrets to specific jobs using permissions: |
| 3 | No .pth file monitoring | Audit site-packages for unexpected .pth files before deployment |
| 4 | No admission control for privileged pods | Pod Security Standards, OPA/Kyverno deny privileged: true |
| 5 | No runtime detection for kube-system anomalies | Falco rules for unexpected pods in system namespaces |
The Lesson:
The irony is devastating: a security scanner — the tool meant to protect your supply chain — became the attack vector. Every defense in this module (SBOM, signing, SLSA, admission control) would have mitigated a different stage of this attack. No single control stops everything. Defense in depth is not optional.
Source: Snyk - Poisoned Security Scanner Backdooring LiteLLM, March 2026.
Dependency Management Security
Section titled “Dependency Management Security”Dependency Confusion Attacks
Section titled “Dependency Confusion Attacks”┌─────────────────────────────────────────────────────────────┐│ DEPENDENCY CONFUSION ATTACK ││ ││ Your package.json: ││ "internal-utils": "1.0.0" ← Private package ││ ││ Attacker publishes to public npm: ││ "internal-utils": "99.0.0" ← Malicious, higher version ││ ││ npm install behavior: ││ "99.0.0 > 1.0.0, use public version!" ← Attack succeeds ││ │└─────────────────────────────────────────────────────────────┘Defenses:
// package.json - Use scoped packages{ "dependencies": { "@mycompany/internal-utils": "1.0.0" }}# .npmrc - Scope to private registry@mycompany:registry=https://npm.mycompany.comTyposquatting Defense
Section titled “Typosquatting Defense”┌─────────────────────────────────────────────────────────────┐│ TYPOSQUATTING ││ ││ Legitimate: Typosquats: ││ lodash 1odash (one not L) ││ express expres (missing s) ││ moment momnet (transposed) ││ react reakt, reactt ││ │└─────────────────────────────────────────────────────────────┘Defenses:
# Use lockfilesnpm ci # Install from lockfile exactlypip install --require-hashes -r requirements.txt
# Verify package checksums# requirements.txt with hashesrequests==2.28.0 \ --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5...Lockfile Best Practices
Section titled “Lockfile Best Practices”| Language | Lockfile | Command |
|---|---|---|
| npm | package-lock.json | npm ci |
| yarn | yarn.lock | yarn install --frozen-lockfile |
| pip | requirements.txt (with hashes) | pip install --require-hashes |
| poetry | poetry.lock | poetry install |
| go | go.sum | go mod verify |
Renovate/Dependabot Security Updates
Section titled “Renovate/Dependabot Security Updates”{ "extends": ["config:base"], "vulnerabilityAlerts": { "enabled": true, "labels": ["security"] }, "packageRules": [ { "matchUpdateTypes": ["patch", "minor"], "matchCurrentVersion": "!/^0/", "automerge": true }, { "matchPackagePatterns": ["*"], "matchUpdateTypes": ["major"], "labels": ["major-update"] } ]}Build Provenance and Reproducibility
Section titled “Build Provenance and Reproducibility”What is Provenance?
Section titled “What is Provenance?”Provenance answers: “Where did this artifact come from?”
{ "_type": "https://in-toto.io/Statement/v0.1", "subject": [{ "name": "myapp", "digest": {"sha256": "abc123..."} }], "predicateType": "https://slsa.dev/provenance/v0.2", "predicate": { "builder": { "id": "https://github.com/myorg/myapp/actions/runs/123" }, "buildType": "https://github.com/slsa-framework/slsa-github-generator", "invocation": { "configSource": { "uri": "git+https://github.com/myorg/myapp@refs/tags/v1.0.0", "digest": {"sha1": "def456..."}, "entryPoint": ".github/workflows/release.yml" } }, "materials": [{ "uri": "git+https://github.com/myorg/myapp@refs/tags/v1.0.0", "digest": {"sha1": "def456..."} }] }}Reproducible Builds
Section titled “Reproducible Builds”Goal: Same source → Same binary (bit-for-bit identical)
Challenges:
- Timestamps in binaries
- Random build IDs
- Non-deterministic ordering
- Environment differences
Solutions:
# Dockerfile: Reproducible buildsFROM golang:1.21 AS builder
# Pin versionsENV CGO_ENABLED=0ENV GOOS=linux
# Use specific commit, not branchCOPY go.mod go.sum ./RUN go mod download
COPY . .
# Reproducible build flagsRUN go build \ -ldflags="-s -w -buildid=" \ -trimpath \ -o /app
# Minimal runtimeFROM scratchCOPY --from=builder /app /appENTRYPOINT ["/app"]# GitHub Actions: Consistent environmentjobs: build: runs-on: ubuntu-22.04 # Pin runner version env: SOURCE_DATE_EPOCH: 0 # Reproducible timestampsCommon Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Solution |
|---|---|---|
| No SBOM | Can’t answer “are we affected?” | Generate SBOM for every build |
| SBOM not stored | Can’t query old versions | Store SBOMs with artifacts |
| Unsigned images | Can’t verify origin | Sign with Cosign/Sigstore |
| No admission control | Unsigned images can deploy | Require signatures in cluster |
Using latest tags | Non-reproducible | Pin versions, use digests |
| No lockfile | Dependency confusion | Lock all dependencies |
| Trust public packages | Typosquatting, hijacking | Verify sources, use SCA |
Quiz: Check Your Understanding
Section titled “Quiz: Check Your Understanding”Question 1
Section titled “Question 1”Your SBOM shows you use log4j 2.14.1. A CVE is announced for log4j 2.x < 2.17. You don’t directly import log4j. What’s happening and what should you do?
Show Answer
What’s happening: Log4j is a transitive dependency—something you depend on depends on it.
Investigation:
# Find the dependency path# Mavenmvn dependency:tree -Dincludes=org.apache.logging.log4j
# Gradlegradle dependencies | grep -A5 log4j
# npmnpm ls log4jResolution options:
-
Update the direct dependency: If
spring-boot-starterpulls in log4j, update spring-boot-starter -
Force version override:
<!-- Maven --><dependencyManagement><dependencies><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.17.1</version></dependency></dependencies></dependencyManagement> -
Exclude and add fixed version:
<dependency><groupId>com.example</groupId><artifactId>some-library</artifactId><exclusions><exclusion><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId></exclusion></exclusions></dependency>
Key lesson: This is why SBOMs matter—they show transitive dependencies SAST can’t see.
Question 2
Section titled “Question 2”You’re implementing image signing. A developer asks: “If we’re already scanning images for vulnerabilities, why do we need signing too?”
Show Answer
Scanning and signing solve different problems:
Vulnerability scanning answers:
- Does this image have known CVEs?
- Are the dependencies up to date?
- Are there misconfigurations?
Image signing answers:
- Was this image built by our CI/CD?
- Has it been tampered with since build?
- Can I trust the image source?
Scenarios where scanning isn’t enough:
-
Registry compromise: Attacker replaces your nginx:v1 with backdoored image. Scan passes (no known CVEs), but it’s not YOUR image.
-
Build system compromise: Malware injected during build. Scan can’t detect custom backdoors without signatures.
-
Unsigned old image: Someone deploys an ancient, unscanned image. Signing policy prevents this entirely.
Defense in depth:
Build → Scan → Sign → Push → Verify → Deploy │ │ │ Find CVEs Prove origin Enforce policyBoth are needed. Scanning finds vulnerabilities. Signing proves authenticity.
Question 3
Section titled “Question 3”What SLSA level is achieved by: “We build in GitHub Actions, and our workflows are in the repository”?
Show Answer
This achieves approximately SLSA Level 2, but verification is needed:
SLSA Level 1 requirements (met):
- ✓ Scripted build (GitHub Actions workflow)
- ✓ Provenance generated (GitHub attestation)
SLSA Level 2 requirements (partially met):
- ✓ Hosted build service (GitHub)
- ⚠️ Authenticated provenance (needs Sigstore or GitHub Attestations)
SLSA Level 3 requirements (not met):
- ✗ Isolated, ephemeral build environment (standard runners are shared)
- ✗ Non-falsifiable provenance (workflow can be modified)
To achieve SLSA Level 3:
# Use SLSA GitHub Generatoruses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0This generates non-falsifiable provenance attesting that:
- Build happened on GitHub Actions
- From specific source commit
- Using specific workflow
- Cannot be forged by the repository owner
Key insight: “Building on GitHub” isn’t enough for L3. The provenance must be non-falsifiable, meaning even the repository owner can’t forge it.
Question 4
Section titled “Question 4”You want to prevent dependency confusion attacks. What controls would you implement?
Show Answer
Multi-layer defense:
1. Package naming:
// Use scoped packages (npm){ "dependencies": { "@mycompany/internal-utils": "1.0.0" }}Scopes are globally unique, can’t be typosquatted.
2. Registry configuration:
@mycompany:registry=https://npm.internal.mycompany.comregistry=https://registry.npmjs.orgalways-auth=trueScoped packages only from internal registry.
3. Lockfile enforcement:
# CI/CDsteps: - run: npm ci # Fails if lockfile doesn't match4. Version pinning in lockfile:
// package-lock.json includes registry URLs"node_modules/@mycompany/internal-utils": { "version": "1.0.0", "resolved": "https://npm.internal.mycompany.com/..."}5. Private package reservation: Register your internal package names on public registries (even if empty) to prevent squatting.
6. Dependency review:
# GitHub Actions- name: Dependency Review uses: actions/dependency-review-action@v3 with: fail-on-severity: high deny-packages: "pkg:npm/internal-*" # Block public internal- packagesHands-On Exercise: Secure Supply Chain Implementation
Section titled “Hands-On Exercise: Secure Supply Chain Implementation”Implement SBOM generation, signing, and verification.
Part 1: Generate SBOM
Section titled “Part 1: Generate SBOM”# Create sample projectmkdir supply-chain-demo && cd supply-chain-demo
cat > Dockerfile << 'EOF'FROM python:3.11-slimWORKDIR /appCOPY requirements.txt .RUN pip install -r requirements.txtCOPY app.py .CMD ["python", "app.py"]EOF
cat > requirements.txt << 'EOF'flask==2.3.0requests==2.28.0EOF
cat > app.py << 'EOF'from flask import Flaskapp = Flask(__name__)
@app.route('/')def hello(): return "Hello, World!"
if __name__ == '__main__': app.run()EOF
# Build imagedocker build -t supply-chain-demo:v1 .
# Generate SBOM with Syftsyft supply-chain-demo:v1 -o spdx-json > sbom.spdx.json
# View SBOM componentscat sbom.spdx.json | jq '.packages[].name'
# Scan SBOM for vulnerabilitiesgrype sbom:./sbom.spdx.jsonPart 2: Sign Image with Cosign
Section titled “Part 2: Sign Image with Cosign”# Install cosignbrew install cosign # or download from GitHub releases
# Login to registry (using Docker Hub for demo)docker login
# Tag and pushdocker tag supply-chain-demo:v1 yourusername/supply-chain-demo:v1docker push yourusername/supply-chain-demo:v1
# Sign with keyless signing (opens browser)COSIGN_EXPERIMENTAL=1 cosign sign yourusername/supply-chain-demo:v1
# Verify signaturecosign verify yourusername/supply-chain-demo:v1 \ --certificate-identity your-email@example.com \ --certificate-oidc-issuer https://accounts.google.comPart 3: Attach SBOM as Attestation
Section titled “Part 3: Attach SBOM as Attestation”# Attach SBOM to imagecosign attest --predicate sbom.spdx.json \ --type spdxjson \ yourusername/supply-chain-demo:v1
# Verify attestation existscosign verify-attestation yourusername/supply-chain-demo:v1 \ --type spdxjson \ --certificate-identity your-email@example.com \ --certificate-oidc-issuer https://accounts.google.com
# Download and inspect SBOMcosign download attestation yourusername/supply-chain-demo:v1 | jq '.payload | @base64d | fromjson'Success Criteria
Section titled “Success Criteria”- SBOM generated for container image
- Vulnerabilities identified from SBOM
- Image signed with Cosign
- Signature verified successfully
- SBOM attached as attestation
Key Takeaways
Section titled “Key Takeaways”- SBOM is essential — You can’t secure what you can’t see
- Sign everything — Prove artifacts come from your build system
- Verify at admission — Don’t let unsigned images deploy
- SLSA provides a framework — Progressive levels for supply chain maturity
- Defense in depth — Lockfiles, scopes, signing, verification all work together
Further Reading
Section titled “Further Reading”Frameworks:
- SLSA — slsa.dev
- OpenSSF Scorecard — securityscorecards.dev
- CNCF Supply Chain Security — github.com/cncf/tag-security
Tools:
- Sigstore — sigstore.dev
- Syft — github.com/anchore/syft
- Cosign — github.com/sigstore/cosign
Standards:
- SPDX — spdx.dev
- CycloneDX — cyclonedx.org
- in-toto — in-toto.io
Summary
Section titled “Summary”Supply chain security protects against attacks on everything except your own code:
- SBOM documents what’s in your software (know your ingredients)
- Image signing proves artifacts come from your build (verify origin)
- SLSA provides maturity levels (progressive improvement)
- Admission control enforces policies at deploy time (trust but verify)
The goal is end-to-end integrity: from source code to running container, every step is verified and every artifact is traceable.
Next Steps
Section titled “Next Steps”- Supply Chain Defense Guide — Project-agnostic checklist covering CI/CD hardening, dependency management, container security, runtime defense, and incident response preparation
- Module 4.5: Runtime Security — Protecting running applications and detecting threats in production
“Trust, but verify. Then verify again at deploy time.”