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

# .github/workflows/codeql.yml name: CodeQL on: push: branches: [main] pull_request: branches: [main] schedule: - cron: '0 3 * * 1' # weekly scan — catches newly published CVE patterns permissions: actions: read contents: read security-events: write # required — uploads SARIF results to the Security tab jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest strategy: matrix: language: [javascript-typescript, java-kotlin] # Other options: python, go, ruby, csharp, cpp, swift steps: - uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: security-extended # default | security-extended | security-and-quality - name: Autobuild uses: 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 Analysis uses: github/codeql-action/analyze@v3 with: category: /language:${{ matrix.language }}

Query packs — choosing depth vs speed

PackWhat it includesScan timeUse for
defaultHigh-precision security queries onlyFastestPRs where speed matters; low false-positive rate
security-extendedDefault + medium-confidence security queriesModerateGood balance for most teams; recommended starting point
security-and-qualitysecurity-extended + code quality queries (dead code, bad patterns)SlowestWeekly 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 pack queries: security-extended config-file: .github/codeql/codeql-config.yml
# .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

SeverityTypical examplesAction
Critical / HighSQL injection, path traversal, SSRF, hardcoded credentialsBlock the PR. Fix before merge. No exceptions without security lead approval.
MediumMissing input validation, weak crypto, open redirectsFix within the sprint. Can merge with a tracked issue if fixing is non-trivial.
Low / WarningLogging sensitive data, deprecated API useScheduled 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.

Semgrep

# .github/workflows/semgrep.yml 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 # required for OIDC signing attestations: write # required to write attestation to the repo 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 # 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 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 # 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 release 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

FormatMaintained byTooling supportBest for
SPDXLinux FoundationBroad; GitHub nativeOpen source compliance; licence scanning; government requirements
CycloneDXOWASPExcellent; most security toolsVulnerability 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 # required for keyless signing 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 }} # --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.

Dependabot for GitHub Actions

# .github/dependabot.yml — actions ecosystem 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.

# 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

Auditing un-pinned actions across your org

# Find all workflow files using mutable tags (not SHAs) $ 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]" # matches @main @v4 etc (not @SHA)
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

# .github/workflows/scorecard.yml on: schedule: - cron: '0 4 * * 1' # weekly 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 # publishes to OpenSSF database (public repos) - uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif

Scorecard checks and what they measure

Branch-Protection
9/10
Code-Review
8/10
Pinned-Dependencies
5/10
Token-Permissions
3/10
Signed-Releases
1/10
Vulnerabilities
10/10
SAST
8/10
Dependency-Update-Tool
10/10
CI-Tests
10/10
CII-Best-Practices
2/10

Scorecard improvement roadmap

The three checks with the highest ROI for most teams:

  1. Pinned-Dependencies — add dependabot.yml with github-actions ecosystem, start SHA-pinning actions. Dependabot keeps the SHAs current.
  2. 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.
  3. 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.
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/acme/platform/badge)](https://securityscorecards.dev/viewer/?uri=github.com/acme/platform)

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