# Supply Chain Security

Protecting the chain from source code to deployed artifact. Every link — CI/CD pipelines, dependency managers, package registries, build caches, release processes — is an attack surface. Compromising any one link can inject malicious code into the final product without touching the source repository.

## Attack surfaces

**CI/CD pipelines.** GitHub Actions, GitLab CI, and similar systems execute code with access to secrets and publishing credentials. Dangerous trigger types — see the [[pwn-request-pattern]] — expose privileged contexts to external code. Mutable action tags let attackers silently replace action code. Build caches can be poisoned to inject payloads into future builds ([[github-actions-cache-poisoning]]). Once code is running on a CI runner, OIDC tokens minted in-process can be extracted from memory and used to publish directly ([[ci-runner-token-extraction]]).

**Dependencies.** A compromised upstream package poisons everyone who depends on it. The window right after a new release is especially dangerous — the [[open-source-security-astral|Astral post]] describes dependency cooldowns as a mitigation: delay updating to avoid catching a temporarily compromised version.

**Package registries.** Long-lived API tokens for PyPI, NPM, crates.io are a common takeover vector. Account compromise or token theft lets attackers publish malicious versions under a trusted package name.

**Release artifacts.** Tags can be force-pushed, releases modified after publication, binary downloads served from mutable URLs. The Trivy supply chain attack used tag force-pushing to replace a legitimate release.

## Defense patterns

**Eliminate credentials where possible.** Trusted Publishing (OIDC-based identity federation) lets CI publish to registries without stored tokens. The registry trusts the CI provider's identity assertion instead. This is one instance of the broader [[ephemeral-credentials]] pattern — see [[long-lived-keys]] for the case that registry tokens are exactly the kind of credential you should never have in the first place.

**Pin everything.** Actions pinned to commit SHAs, dependency versions locked, download URLs mapped to cryptographic hashes. Mutability is the enemy — anywhere a pointer can be silently redirected, an attacker can substitute malicious content.

**Minimal permissions.** Start with zero permissions, grant only what each job needs. Isolate secrets into deployment environments rather than making them available repo-wide.

**Attestation and verification.** Sigstore creates cryptographic links between artifacts and their producing workflows. Users can verify that what they downloaded was actually built by the expected CI pipeline.

**Immutable releases.** Once published, artifacts and tags can't be modified. This blocks the "replace the release after the fact" attack pattern.

**Multi-person approval.** Release processes require sign-off from more than one person, mitigating single-account compromise.

**Dependency cooldowns.** Deliberately delay dependency updates after new releases. Most supply chain poisoning through dependencies is discovered quickly — waiting a few days filters out the worst.

**Social relationships.** Know your upstream maintainers. Contribute security improvements to their pipelines. Participate in ecosystem security teams. Supply chain security is partly a coordination problem.

## Tools

- **zizmor** — static analysis for GitHub Actions workflows, catches unpinned actions and impostor commits
- **pinact** — automated action pinning to commit SHAs
- **Sigstore** — keyless signing and verification for artifacts
- **Trusted Publishing** — OIDC-based credential-free publishing (PyPI, crates.io, NPM)
- **Dependabot / Renovate** — automated dependency updates with cooldown support

## Maintainer-trust attacks

Not every supply-chain risk is a credential or pipeline compromise. The hardest class to defend against is **maintainer-trust capture** — a contributor accumulating legitimate access over time and then misusing it, as in [[xz-utils-incident|xz-utils]]. Its sibling failure mode, [[maintainer-governance-ambiguity]], is what makes even legitimate maintainer transitions look like attacks now, and forces downstream users into expensive verification cycles when projects don't make access changes auditable. Related: [[unmaintained-scanner-pressure]] is the forcing function that pressures stable projects into the very kind of "new maintainer steps in" event that, post-xz, reads as an attack from outside.

## Notable incidents

- **2026-05 fsnotify — maintainer dispute mistaken for takeover.** A 321k-dependent Go library; access removals between [[arp242]] and [[mattn]] read from outside as a possible supply-chain incident. No code compromise. Worked example of [[maintainer-governance-ambiguity]] and post-xz outside-observer cost. See [[fsnotify-maintainer-dispute]].

- **2026-05-11 TanStack — `@tanstack/*` npm scope.** 84 malicious versions across 42 packages published in a 6-minute window via a three-link CI chain: a `pull_request_target` workflow that built fork code ([[pwn-request-pattern]]), Actions cache poisoning across the fork→base→production trust boundary ([[github-actions-cache-poisoning]]), and OIDC token extraction from runner memory ([[ci-runner-token-extraction]]). No npm tokens were stolen; the legitimate `Publish Packages` step never ran. External detection within ~20 minutes via Socket.dev and an open issue. See [[tanstack-npm-supply-chain-postmortem]] for the chain and the lessons specific to each defense.

- **2026 Shai-Hulud / Checkmarx — Bitwarden CLI.** [[bitwarden|Bitwarden]]'s `@bitwarden/cli` 2026.4.0 was published to npm with a malicious `bw1.js` payload via a compromised GitHub Action in their CI/CD pipeline. The payload pulled the Bun runtime, decrypted the Shai-Hulud worm, and harvested GitHub/npm tokens, SSH keys, shell history, AWS/GCP/Azure credentials, GitHub Actions secrets, and MCP configs — exfiltrated by auto-creating a public repo on each victim's own GitHub account. Live for ~19 hours; 334 developers affected. Two compounding factors: a security-critical CLI distributed through npm rather than as a single static binary, and a CI/CD pipeline that didn't catch the injected file before publish. See [[marius-bitwarden-not-recommended]] for the broader Bitwarden context.

## See also

- [[open-source-security-astral]] — Astral's detailed implementation of these patterns
- [[long-lived-keys]] — Ludwig's argument that the registry-token problem generalizes to all long-lived keys
- [[ephemeral-credentials]] — the underlying pattern Trusted Publishing instantiates
- [[hazmat]] — runtime containment for AI coding agents (a different layer of the trust problem)
- [[living-off-the-land]] — runtime analogue: attackers using legitimate pre-installed binaries instead of dropping malware
- [[gtfobins]] — canonical Unix catalog of LotL-abusable binaries, both red-team reference and sysadmin checklist
- [[toolbox/dockerscan|dockerscan]] — a concrete scanner covering several of these surfaces (CIS benchmark, supply-chain, secrets, CVE, runtime) for Docker images
