Stage 5 · Platform
Supply-Chain Security
Pipeline Threat Modeling
Mapping attack paths across pull requests, third-party actions, registries, caches, and deploy keys.
Threat Modeling Concept
Threat modeling identifies potential attack vectors in your pipeline and evaluates the risk of each. A pipeline threat model maps the flow of code from developer commit to production deployment and identifies where an attacker could inject malicious code or steal credentials.
The STRIDE framework adapted for CI/CD: Spoofing (fake commits), Tampering (modifying artifacts), Repudiation (hiding changes), Information Disclosure (leaking secrets), Denial of Service (resource exhaustion), and Elevation of Privilege (escalating runner access).
Pipeline Attack Vectors
# Attack vectors in a CI/CD pipeline
# 1. Source Code
# - Compromised developer credentials
# - Malicious pull requests
# - Force-pushed history
# - Compromised git hooks
# 2. Pipeline Configuration
# - Modified workflow files
# - Injected build steps
# - Malicious environment variables
# 3. Dependencies
# - Typosquatting packages
# - Compromised package registries
# - Dependency confusion attacks
# - Malicious transitive dependencies
# 4. Build Process
# - Compromised build tools
# - Malicious build scripts
# - Build cache poisoning
# - Compiler attacks
# 5. Artifacts
# - Tampered container images
# - Modified binaries
# - Injected malware
# 6. Deployment
# - Compromised deploy credentials
# - Man-in-the-middle attacks
# - Registry tampering
# 7. Runtime
# - Container escape
# - Secret theft
# - Lateral movementThis map identifies every stage of the pipeline as a potential attack vector. Each vector requires specific mitigations. The most common attack vectors are compromised dependencies, malicious actions, and credential theft.
Third-Party Actions
jobs:
build:
runs-on: ubuntu-latest
steps:
# Pin actions to full commit SHA, not tags
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: "20"
# Use official actions or pinned forks
- uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
with:
push: true
tags: ghcr.io/myorg/myapp:${{ github.sha }}
# Never use actions from untrusted sources
# Avoid: uses: random-user/my-action@main
---
# Action pinning verification
name: Verify Action Pins
on: pull_request
jobs:
check-pins:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check for unpinned actions
run: |
UNPINNED=$(grep -r "uses:" .github/workflows/ | grep -v "@" | grep -v "#" || true)
if [ -n "$UNPINNED" ]; then
echo "::error::Found unpinned actions:"
echo "$UNPINNED"
exit 1
fi
# Check for tag references (not SHA)
TAG_REFS=$(grep -r "uses:" .github/workflows/ | grep -E "@v[0-9]" | grep -v "#" || true)
if [ -n "$TAG_REFS" ]; then
echo "::warning::Found tag-based action references (prefer SHA pinning):"
echo "$TAG_REFS"
fiPin actions to full commit SHAs instead of tags. Tags can be moved to point to malicious code. SHA pinning ensures the action code is immutable. Use tools like Dependabot to keep pinned versions updated.
Registry Attacks
# Image verification before deploy
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: sigstore/cosign-installer@v3
- name: Verify image signature
run: |
cosign verify \
--certificate-identity=https://github.com/myorg/myrepo/.github/workflows/deploy.yml@refs/heads/main \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
ghcr.io/myorg/myapp:${{ github.sha }}
- name: Verify image digest
run: |
# Ensure the image has not been mutated
DIGEST=$(crane digest ghcr.io/myorg/myapp:${{ github.sha }})
EXPECTED=$(crane digest ghcr.io/myorg/myapp@sha256:${{ github.sha }})
if [ "$DIGEST" != "$EXPECTED" ]; then
echo "::error::Image digest mismatch"
exit 1
fi
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/myorg/myapp:${{ github.sha }}
severity: CRITICAL
exit-code: 1Multiple verification layers protect against registry attacks. Signature verification confirms the image was built by a trusted pipeline. Digest verification prevents tag mutation. Vulnerability scanning catches known CVEs.
Cache Poisoning
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Use immutable cache keys
- name: Cache dependencies
uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
deps-${{ runner.os }}-
# Never restore cache from PRs to main
- name: Cache build outputs
uses: actions/cache@v4
with:
path: dist/
key: build-${{ github.sha }}
restore-keys: build-${{ github.event.before }}
# Verify cache integrity
- name: Verify cache integrity
run: |
if [ -f node_modules/.cache-key ]; then
CACHED_KEY=$(cat node_modules/.cache-key)
EXPECTED_KEY=$(cat package-lock.json | sha256sum | awk '{print $1}')
if [ "$CACHED_KEY" != "$EXPECTED_KEY" ]; then
echo "::warning::Cache key mismatch, clearing cache"
rm -rf node_modules
npm ci
fi
fiCache poisoning occurs when an attacker modifies cached dependencies to inject malicious code. Use immutable keys based on lock file hashes. Verify cache integrity before using cached data.
Mitigation Strategies
- Pin actions to SHA — prevents tag-based attacks on third-party actions.
- Enable OIDC for cloud access — short-lived tokens limit credential exposure.
- Scan dependencies on every PR — catch vulnerable packages before they merge.
- Sign and verify artifacts — ensure artifacts are tamper-free from build to deploy.
- Isolate runners — ephemeral runners, network isolation, and least-privilege tokens.
- Audit everything — log all pipeline events and review regularly.
SLSA (Supply-chain Levels for Software Artifacts) provides a structured approach to supply chain security. Start at SLSA Level 1 (provenance exists) and progress to Level 3 (non-falsifiable provenance). Each level adds security guarantees.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.