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
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 stageDeveloper machine→Source code→CI/CD pipeline→Artifact registry→Production↓↓↓↓↓stolen credsvuln codetampered buildartifact swapconfig drift↓↓↓↓↓secret scanningCodeQL/SASTattestationscosign signingSBOM/provenance
8.2
CodeQL: SARIF Format, codeql-action & Query Packs
📚 Free Weekly Tutorials
Java, Spring Boot, AWS, DevOps & AI — straight to your inbox.
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
# .github/workflows/codeql.ymlname:CodeQLon:push:branches: [main]
pull_request:branches: [main]
schedule:
- cron:'0 3 * * 1'# weekly scan — catches newly published CVE patternspermissions:actions:readcontents:readsecurity-events:write# required — uploads SARIF results to the Security tabjobs:analyze:name:Analyze (${{ matrix.language }})runs-on: ubuntu-latest
strategy:matrix:language: [javascript-typescript, java-kotlin]
# Other options: python, go, ruby, csharp, cpp, swiftsteps:
- uses:actions/checkout@v4
- name:Initialize CodeQLuses:github/codeql-action/init@v3with:languages:${{ matrix.language }}queries:security-extended# default | security-extended | security-and-quality
- name:Autobuilduses:github/codeql-action/autobuild@v3# For compiled languages (Java, C++), autobuild detects your build system# For interpreted languages (JS, Python), this step is a no-op
- name:Perform CodeQL Analysisuses:github/codeql-action/analyze@v3with: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:
# Project layout for custom queries
.github/
codeql/
custom-queries/
javascript/
hardcoded-jwt-secret.ql
qlpack.yml ← declares the query pack# In codeql.yml — add custom queries alongside the standard packqueries:security-extendedconfig-file:.github/codeql/codeql-config.yml
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:
# Branch protection — required status checks to add:
CodeQL / Analyze (javascript-typescript)
CodeQL / Analyze (java-kotlin)
# In Settings → Code security → Code scanning → Protection rules:# Set "Block PRs that introduce alerts with severity" to "High or higher"
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.
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:readid-token:write# required for OIDC signingattestations:write# required to write attestation to the reposteps:
- uses:actions/checkout@v4
- name:Build artifactrun:npm run build && tar -czf app.tar.gz dist/
- name:Attest build provenanceuses:actions/attest-build-provenance@v1with:subject-path:app.tar.gz# For multiple files:# subject-path: |# dist/*.js# app.tar.gz
Verifying an attestation
# Verify that app.tar.gz was built by the expected workflow$ 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 imageid: push
uses:docker/build-push-action@v6with:push:truetags:ghcr.io/acme/app:${{ github.sha }}
- name:Attest image provenanceuses:actions/attest-build-provenance@v1with:subject-name:ghcr.io/acme/appsubject-digest:${{ steps.push.outputs.digest }}push-to-registry:true# attaches attestation to the image in GHCR
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:
# Download SBOM via REST API (SPDX format)$ gh api repos/acme/platform/dependency-graph/sbom \
--jq '.sbom' > sbom.spdx.json
CycloneDX via GitHub Actions (richer, more tooling support)
# Generate CycloneDX SBOM for a Node.js project and attach to releasejobs:sbom:runs-on: ubuntu-latest
permissions:contents:writeid-token:writeattestations:writesteps:
- uses:actions/checkout@v4
- name:Generate CycloneDX SBOMuses:CycloneDX/gh-node-module-generatebom@v1with:path:./output:bom.json
- name:Attest SBOMuses:actions/attest-sbom@v1with:subject-path:dist/app.tar.gzsbom-path:bom.json
- name:Upload SBOM to releaseuses:softprops/action-gh-release@v2with: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
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:readpackages:writeid-token:write# required for keyless signingsteps:
- uses:actions/checkout@v4
- name:Install cosignuses:sigstore/cosign-installer@v3
- name:Log in to GHCRuses:docker/login-action@v3with:registry:ghcr.iousername:${{ github.actor }}password:${{ secrets.GITHUB_TOKEN }}
- name:Build and pushid: build
uses:docker/build-push-action@v6with:push:truetags:ghcr.io/acme/app:${{ github.sha }}
- name:Sign image (keyless)run: |
cosign sign --yes \
ghcr.io/acme/app@${{ steps.build.outputs.digest }}
# --yes skips the interactive prompt in CI# The OIDC token is used automatically from the runner environment
Verifying a signed image
# Verify the image was signed by our GitHub Actions workflow$ 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.
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.
# What the Dependabot PR looks like in your workflow diff:- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ uses: actions/checkout@v4.2.3 # SHA: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0# Dependabot also updates to the new SHA and adds the version tag comment
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.
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.