PHASE 8 OF 14
Supply Chain Security
The software supply chain threat model, CodeQL static analysis, third-party SAST via SARIF, artifact attestations, SBOM generation, Sigstore/cosign container signing, and OpenSSF Scorecard — the tools that prove your build pipeline hasn't been tampered with
Supply Chain
CodeQL
SARIF
Attestations
SBOM
Sigstore
OpenSSF
8.1
Software Supply Chain Threat Model
The SolarWinds, Log4Shell, and XZ Utils incidents made supply chain security a board-level conversation. Before investing in tooling, it's worth understanding the distinct attack classes you're defending against — they require different mitigations.
📦
Dependency confusion
An attacker publishes a malicious package to a public registry (npm, PyPI) with the same name as your internal private package. If your build system checks public registries first, it installs the attacker's version. Mitigation: namespace scoping (@acme/), registry pinning, lockfile verification.
🔎
Typosquatting
A developer types reqeusts instead of requests and installs a malicious package. Mitigation: dependency review action (Phase 7), mandatory lockfiles committed to the repo, code review of package.json / pom.xml changes.
😈
Compromised maintainer
A popular package maintainer is coerced, hacked, or burns out and sells the package to a bad actor who ships a malicious update. Mitigation: SHA-pinned dependencies, lockfiles, Sigstore provenance verification, monitoring for unexpected major version bumps.
🛠️
Build pipeline tampering
An attacker gains access to your CI system and modifies the build pipeline to inject malicious code into your artifacts — without touching your source code at all. Mitigation: artifact attestations prove a given artifact was built by a specific workflow from a specific commit, on a specific runner.
📜
Code vulnerabilities in your own software
SQL injection, path traversal, deserialization bugs — your own code introducing vulnerabilities that downstream consumers inherit. Mitigation: CodeQL and SAST scanning in CI, blocking PRs on new high-severity findings.
Your software supply chain — attack surfaces at each stage
Developer machine → Source code → CI/CD pipeline → Artifact registry → Production
↓ ↓ ↓ ↓ ↓
stolen creds vuln code tampered build artifact swap config drift
↓ ↓ ↓ ↓ ↓
secret scanning CodeQL/SAST attestations cosign signing SBOM/provenance
8.2
CodeQL: SARIF Format, codeql-action & Query Packs
CodeQL is GitHub's semantic code analysis engine. Unlike grep-based scanners, CodeQL builds a queryable database of your code's structure — it understands data flow, taint tracking, and call graphs. This lets it find vulnerabilities that span multiple files and function calls.
Setting up the default CodeQL scan
name: CodeQL
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 3 * * 1'
permissions:
actions: read
contents: read
security-events: write
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
strategy:
matrix:
language: [javascript-typescript, java-kotlin]
steps:
- uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
queries: security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:${{ matrix.language }}
Query packs — choosing depth vs speed
| Pack | What it includes | Scan time | Use for |
default | High-precision security queries only | Fastest | PRs where speed matters; low false-positive rate |
security-extended | Default + medium-confidence security queries | Moderate | Good balance for most teams; recommended starting point |
security-and-quality | security-extended + code quality queries (dead code, bad patterns) | Slowest | Weekly scheduled scans; deeper analysis without PR blocking |
Custom CodeQL queries
You can write your own queries in CodeQL's query language (QL) and add them to the scan:
.github/
codeql/
custom-queries/
javascript/
hardcoded-jwt-secret.ql
qlpack.yml
queries: security-extended
config-file: .github/codeql/codeql-config.yml
name: Acme CodeQL config
queries:
- uses: security-extended
- uses: ./.github/codeql/custom-queries
paths-ignore:
- node_modules
- vendor
- '**/*.test.ts'
- dist
Reviewing code scanning results
Results appear in Security → Code scanning alerts. Each alert shows:
- The vulnerable code path with highlighted source and sink
- The data flow path (how untrusted input reaches the vulnerable point)
- Rule ID, CWE category, and severity
- Inline annotation in the PR diff for new alerts
Dismiss alerts with a reason (false positive, used in tests, risk accepted) — all dismissals are audited. Never silently dismiss a real finding.
8.3
Code Scanning Alerts: Severity, Dismissal & Copilot Autofix
Alert severity levels and what to do
| Severity | Typical examples | Action |
| Critical / High | SQL injection, path traversal, SSRF, hardcoded credentials | Block the PR. Fix before merge. No exceptions without security lead approval. |
| Medium | Missing input validation, weak crypto, open redirects | Fix within the sprint. Can merge with a tracked issue if fixing is non-trivial. |
| Low / Warning | Logging sensitive data, deprecated API use | Scheduled cleanup. Document acceptance if intentional. |
Blocking PRs on new high-severity alerts
Make code scanning a required status check in your branch protection rules. New Critical/High findings on a PR will block merge:
CodeQL / Analyze (javascript-typescript)
CodeQL / Analyze (java-kotlin)
Copilot Autofix for code scanning
GitHub Copilot Autofix (available with GHAS) analyzes a code scanning alert and suggests a fix inline in the PR diff. The suggestion is a pull-request suggestion that the author can accept with one click. It understands the alert's data flow path and generates context-aware patches rather than generic template fixes.
REVIEW AUTOFIX SUGGESTIONS CAREFULLY
Autofix suggestions are good starting points but not guaranteed to be correct. They may fix the symptom without addressing the root cause, or introduce a new issue. Always review the diff before applying — treat it like any other reviewer suggestion.
8.4
Third-Party SAST Tools via SARIF Upload
SARIF (Static Analysis Results Interchange Format) is a JSON standard for security tool output. Any tool that produces SARIF can upload results to GitHub's Security tab — giving you a unified view of findings across CodeQL, Semgrep, Snyk, Trivy, and others.
Semgrep
on:
push:
branches: [main]
pull_request:
permissions:
security-events: write
contents: read
jobs:
semgrep:
runs-on: ubuntu-latest
container:
image: semgrep/semgrep
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
run: semgrep scan --config auto --sarif --output semgrep.sarif
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: semgrep.sarif
category: semgrep
Trivy (container image and filesystem scanning)
jobs:
trivy:
runs-on: ubuntu-latest
permissions:
security-events: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Run Trivy on filesystem
uses: aquasecurity/trivy-action@master
with:
scan-type: fs
scan-ref: .
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
ignore-unfixed: true
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: trivy-results.sarif
category: trivy-fs
CATEGORY MATTERS
The
category: field in
upload-sarif namespaces the results. If you upload results from two tools to the same category, the second upload overwrites the first. Always set distinct categories:
codeql,
semgrep,
trivy-fs,
trivy-container. Each category gets its own column in the Security tab.
8.5
Artifact Attestations: Build Provenance
An artifact attestation is a signed statement that a specific artifact (binary, container image, npm package) was produced by a specific GitHub Actions workflow, from a specific commit, at a specific time. It answers "did this artifact really come from our official build pipeline, or was it tampered with?"
GitHub's attestation system uses Sigstore under the hood — the attestation is signed with a short-lived certificate tied to the GitHub OIDC token, and the signature is published to Sigstore's public transparency log.
Generating build provenance attestation
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- name: Build artifact
run: npm run build && tar -czf app.tar.gz dist/
- name: Attest build provenance
uses: actions/attest-build-provenance@v1
with:
subject-path: app.tar.gz
Verifying an attestation
$ gh attestation verify app.tar.gz \
--repo acme/platform \
--signer-workflow .github/workflows/build.yml
Loaded digest sha256:a3f8d12... for file://app.tar.gz
Loaded 1 attestation from GitHub API
The following policy checks passed:
✓ artifact digest sha256:a3f8d12... matches subject digest
✓ workflow .github/workflows/build.yml matches expected signer workflow
✓ certificate identity matches expected source repository
1 attestation successfully verified.
Container image attestation
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/acme/app:${{ github.sha }}
- name: Attest image provenance
uses: actions/attest-build-provenance@v1
with:
subject-name: ghcr.io/acme/app
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
8.6
SBOM Generation: CycloneDX, SPDX & Attaching to Releases
A Software Bill of Materials (SBOM) is a machine-readable inventory of every component, library, and dependency in your software — like a nutrition label for code. SBOMs are increasingly required for software sold to government agencies and regulated industries, and useful for rapid vulnerability assessment when a new CVE drops.
GitHub's built-in SBOM export
For repos with dependency graph enabled, GitHub can generate a basic SPDX SBOM directly from the UI or API:
$ gh api repos/acme/platform/dependency-graph/sbom \
--jq '.sbom' > sbom.spdx.json
CycloneDX via GitHub Actions (richer, more tooling support)
jobs:
sbom:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- name: Generate CycloneDX SBOM
uses: CycloneDX/gh-node-module-generatebom@v1
with:
path: ./
output: bom.json
- name: Attest SBOM
uses: actions/attest-sbom@v1
with:
subject-path: dist/app.tar.gz
sbom-path: bom.json
- name: Upload SBOM to release
uses: softprops/action-gh-release@v2
with:
files: bom.json
SBOM formats comparison
| Format | Maintained by | Tooling support | Best for |
| SPDX | Linux Foundation | Broad; GitHub native | Open source compliance; licence scanning; government requirements |
| CycloneDX | OWASP | Excellent; most security tools | Vulnerability analysis; component analysis; DevSecOps workflows |
SBOM STRATEGY
Generate a CycloneDX SBOM at build time, attach it to the release artifact, and attest it. When a critical CVE is published, run the SBOM through a vulnerability scanner (
grype bom.json) to instantly know if any of your released versions are affected — no manual dependency trawling required.
8.7
Sigstore & cosign: Signing Container Images
Sigstore is a set of open-source tools (cosign, fulcio, rekor) for signing software artifacts. The key innovation is keyless signing — instead of managing a GPG key pair, you use your OIDC identity (GitHub Actions token) to get a short-lived signing certificate. The signature is anchored to Sigstore's public transparency log (Rekor), making tampering detectable.
Signing a container image with cosign (keyless)
jobs:
build-and-sign:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Log in 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@v6
with:
push: true
tags: ghcr.io/acme/app:${{ github.sha }}
- name: Sign image (keyless)
run: |
cosign sign --yes \
ghcr.io/acme/app@${{ steps.build.outputs.digest }}
Verifying a signed image
$ cosign verify \
--certificate-identity-regexp "https://github.com/acme/platform/.github/workflows/build.yml" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/acme/app@sha256:a3f8d12...
Verification for ghcr.io/acme/app@sha256:a3f8d12... --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate claims were validated
Verifying at deploy time (Kubernetes admission)
Tools like Sigstore Policy Controller and Kyverno can enforce that only signed images from trusted workflows are allowed to run in your Kubernetes cluster — the verification happens at pod admission, before the container starts. This closes the gap between "image was built correctly" and "only signed images run in production."
ATTESTATIONS VS COSIGN SIGNATURES
GitHub's
actions/attest-build-provenance (section 8.5) and
cosign sign are complementary, not alternatives. Attestations prove
what workflow produced the artifact and from which commit. cosign signatures prove
the artifact hasn't been replaced since signing. For maximum supply chain security, do both.
8.8
Pinning Actions to a SHA with Dependabot Automation
We covered SHA pinning in Phase 6.12. Here we focus on the operational workflow — how to keep hundreds of SHA-pinned actions across dozens of repos updated without manual effort.
Dependabot for GitHub Actions
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
all-actions:
patterns: ['*']
commit-message:
prefix: chore
When an action author pushes a new release, Dependabot opens a PR that updates the SHA in your workflow file from the old commit to the new one — with the new version tag in a comment. You review the action's changelog, approve, and merge.
+ uses: actions/checkout@v4.2.3
Auditing un-pinned actions across your org
$ gh search code "uses:" --owner acme \
--extension yml \
--json path,repository \
| jq '.[] | select(.path | contains(".github/workflows"))' \
| xargs -I{} gh api repos/{}/contents/{} --jq '.content' \
| base64 -d \
| grep -E "uses:.*@[a-z]"
8.9
OpenSSF Scorecard: Measuring & Improving Supply Chain Posture
The OpenSSF Scorecard is an automated tool that evaluates a repository against a set of supply chain security checks and produces a score from 0–10. It gives you an objective, auditable measure of your security posture — and a concrete to-do list for improvement.
Running Scorecard in CI
on:
schedule:
- cron: '0 4 * * 1'
push:
branches: [main]
permissions:
security-events: write
id-token: write
contents: read
actions: read
jobs:
analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: ossf/scorecard-action@v2
with:
results_file: results.sarif
results_format: sarif
publish_results: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Scorecard checks and what they measure
Dependency-Update-Tool
10/10
Scorecard improvement roadmap
The three checks with the highest ROI for most teams:
- Pinned-Dependencies — add
dependabot.yml with github-actions ecosystem, start SHA-pinning actions. Dependabot keeps the SHAs current.
- Token-Permissions — add
permissions: contents: read at the top of every workflow. Add more specific permissions per job as needed. This single change often jumps the score by 3+ points.
- Signed-Releases — add
actions/attest-build-provenance to your release workflow. Combined with cosign for container images, this satisfies the check.
SCORECARD BADGE
Once your score is above 7, add the Scorecard badge to your README. It signals to downstream consumers, security auditors, and potential contributors that your supply chain hygiene is measurably good. For public repos, the badge links to the live score on the OpenSSF website.
Up Next — Phase 9: Organization & Team Management
Org structure and team hierarchies, SSO enforcement, GitHub Apps vs OAuth Apps, fine-grained PATs, org-level rulesets, EMU, audit log streaming, and cost governance.
Continue to Phase 9 →
Back to Hub