Home › Security › Operational security
guide low

Software supply chain security

Problem Teams shipping software with third-party dependencies and a deploying CI system must decide how to control what code enters the build and prove what comes out, against malicious packages and compromised build tooling.

Control what code enters your build and prove what comes out: lockfiles, vetted updates, hardened CI, signed artifacts with provenance.

supply-chaindependenciessbomslsasigstoreci-securitylockfiles
Overview

Your binary contains hundreds of packages by strangers and was built by a CI system holding deploy keys. Attackers target both: malicious versions (typosquats, hijacked maintainers, token-stealing worms), backdoored upstreams (xz-utils, 2024), compromised CI actions (tj-actions, 2025). Governments, package registries and cloud platforms all act on it. Defense: pin and review what comes in, limit what CI can do, sign what goes out.

Key facts

Use when

  • Any project with dependencies or CI that can deploy, for example:
    • Government: software bills of materials (CISA SBOM guidance).
    • Package registries: npm provenance statements signed through Sigstore.
    • Cloud infrastructure: Kubernetes signs its release artifacts with keyless cosign; Google Cloud Binary Authorization deploys only attested images.

Avoid when

  • Never skip the basics; SLSA level 3 and reproducible builds are for high-value targets.

Deep dive

Decision table

RiskControlTooling
Known-vulnerable dependencyScan lockfile in CI and on schedulecargo audit/cargo deny, npm audit, pip-audit, govulncheck, OSV-Scanner
Malicious new versionCooldown before upgrading; review diffs of updatesRenovate minimumReleaseAge, Dependabot cooldown, cargo vet
Install-time code executionDisable lifecycle scripts by defaultnpm ci --ignore-scripts, pnpm (scripts off for deps by default in v10)
Typosquatting / dependency confusionScoped/private registry with namespace ownership; explicit registry per scope.npmrc scopes, cargo registries, pip --index-url only
Compromised CI actionPin by SHA, least permissions:, no secrets to fork PRsRenovate pins and updates SHAs
Stolen publish tokensTrusted publishing (OIDC) instead of long-lived tokens; 2FA for maintainersPyPI/npm/crates.io trusted publishing
Tampered artifactSign + provenance, verify at deploycosign, SLSA GitHub generator, npm --provenance
Unknown inventorySBOM per releaseSyft, cargo cyclonedx, CycloneDX/SPDX
Base image driftPin image by digest, rebuild regularly, minimal basedistroless, Chainguard, FROM image@sha256:...

Code

deny.toml for cargo deny check in CI:

[advisories]
version = 2
yanked = "deny"

[licenses]
version = 2
allow = ["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "ISC", "Unicode-3.0", "Zlib"]

[bans]
multiple-versions = "warn"
wildcards = "deny"

[sources]
unknown-registry = "deny"
unknown-git = "deny"

Hardened GitHub Actions workflow:

name: release
on:
  push:
    tags: ["v*"]
permissions: {}                      # default: nothing
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write                # keyless signing via OIDC
    steps:
      - uses: actions/checkout@<full-40-char-sha>        # v4.x, pinned
        with: { persist-credentials: false }
      - run: cargo build --release --locked
      - run: cargo deny check
      - uses: sigstore/cosign-installer@<full-40-char-sha>
      - run: |
          docker build -t ghcr.io/org/app:${GITHUB_REF_NAME} .
          docker push ghcr.io/org/app:${GITHUB_REF_NAME}
          DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' ghcr.io/org/app:${GITHUB_REF_NAME})
          cosign sign --yes "$DIGEST"            # sign the digest, never a mutable tag

Verify at deploy (admission controller or deploy script):

cosign verify ghcr.io/org/app@sha256:<digest> \
  --certificate-identity "https://github.com/org/app/.github/workflows/release.yml@refs/tags/v1.4.0" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com"

npm defaults (.npmrc in the repo):

ignore-scripts=true
save-exact=true

Efficiency

Cost: CI minutes, network fetches and delayed upgrades, not runtime.

Levers:

  • Cache the advisory data — cargo deny check and cargo audit read the lockfile plus a cached advisory database, so they take seconds (order of) when the database is cached between CI runs — every CI.
  • Scan on change and on a schedule — when the lockfile changes plus one daily run, instead of every push — busy repositories.
  • Keyless signing — cosign with OIDC adds a few network calls per release (order of seconds) and removes key storage and rotation; sign the digest once per release, not per build — released artifacts.
  • Fewer dependencies — shrink scan time, review load and attack surface — always.
  • Minimal base images — distroless or Chainguard cut pull size and CVE noise; static images are order of MiB, full distributions order of tens to hundreds of MiB — containers.

Naive → standard → state of the art:

  • Naive: unpinned dependencies, scan on every push · unbounded CI minutes, no protection
  • Standard: committed lockfile, --locked builds, SHA-pinned actions, cargo deny · seconds per run
  • State of the art: add SBOMs, provenance, reproducible builds and keyless signing for high-value artifacts · order of seconds per release

Process

  1. Fewer dependencies: each one is a maintainer you trust with production. Prefer the standard library and well-staffed packages.
  2. Updates via bot PRs, batched weekly, with cooldown; security fixes fast-tracked.
  3. CI on PRs from forks runs without secrets; deploy jobs only on protected branches/tags with required reviews.
  4. Releases: built in CI only (never laptops), signed, SBOM attached, provenance published.
  5. Deploy verifies signature identity and digest; runtime images are immutable.

When a dependency is compromised

  1. Search lockfiles and SBOMs across repos for the bad versions; check build logs for when they were installed.
  2. Treat any CI job or laptop that ran the bad version as compromised: rotate every secret it could read.
  3. Pin to a known-good version, rebuild from clean runners, and redeploy.

Pitfalls

  • Tags are mutable: uses: some/action@v3 or FROM node:22 can change under you.
  • curl | sh installers in CI and Dockerfiles.
  • Lockfile present but CI runs npm install / cargo update, silently ignoring it.
  • pull_request_target workflows checking out and running PR code with secrets.
  • Scanners alerting on hundreds of unreachable CVEs until everyone ignores them; triage by reachability (govulncheck does this) and fix the rest.
  • Signing but never verifying.
  • Vendored code that never gets updated.
  • Developer machines with publish tokens and broad cloud credentials: the worm's favorite target.

In production

  • Government: CISA publishes SBOM guidance and minimum elements for software producers and buyers.
  • Package registries: npm publishes packages with provenance, signed by Sigstore and logged in a public transparency ledger.
  • Cloud infrastructure: the Kubernetes release process signs tarballs, SPDX files and binaries with cosign keyless signing; Binary Authorization admits only images with attestations.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
cargo-denyRustEmbarkStudios/cargo-denycargo install --locked cargo-deny / brew install cargo-deny————advisories, licenses, bans, sources
cargo-auditRustrustsec/rustseccargo install cargo-audit --locked / brew install cargo-audit————scans Cargo.lock against RustSec
cargo-vetRustmozilla/cargo-vetcargo install --locked cargo-vet————records human audits of dependencies
cosignGosigstore/cosignbrew install cosign————keyless signing and verification of images and blobs
SyftGoanchore/syftbrew install syft————SBOM generation (CycloneDX, SPDX)

Sources

  • SLSA, Supply-chain Levels for Software Artifacts specification v1.0, OpenSSF, 2023. spec
  • Newman, Meyers, Torres-Arias, Sigstore: Software Signing for Everybody, ACM CCS 2022. paper · code
  • Ladisa et al., SoK: Taxonomy of Attacks on Open-Source Software Supply Chains, IEEE S&P 2023. paper · arXiv
  • NVD, CVE-2024-3094 (xz-utils backdoor). record
  • Birsan, Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies, 2021.
  • CISA, Software Bill of Materials (SBOM). site
  • npm Docs, Generating provenance statements. docs
  • Kubernetes, Verify Signed Kubernetes Artifacts. docs
  • Google Cloud, Binary Authorization overview. docs

Builds on

Used by

Systems (3):

Alternatives and related