In December 2024, the maintainers of Ultralytics YOLO — a machine learning library downloaded millions of times — discovered that two versions of their package published to PyPI contained a cryptominer. The malicious code hadn't been slipped into their GitHub repository. It had been injected during the automated build-and-publish workflow itself, via a poisoned GitHub Actions cache. The source code was clean. The pipeline that turned that source code into a published artifact was not.
This is the pattern security teams keep missing. Organizations lock down code review, require signed commits, and scan dependencies for known CVEs — then hand a GitHub Actions workflow or a Jenkins job unattended, long-lived credentials to publish directly to npm, PyPI, or a container registry. Attackers have noticed. Over the past four years, CI/CD publishing pipelines have gone from an operational afterthought to one of the most active fronts in software supply chain attacks, and the incidents keep escalating in both frequency and blast radius.
Why are publishing pipelines a more attractive target than source code?
Because compromising a pipeline gives an attacker everything a compromised maintainer account would, without needing to touch the maintainer at all. A pull request against a popular repository gets reviewed. A GitHub Action that runs on every merge, with secrets.NPM_TOKEN or PYPI_API_TOKEN sitting in its environment, often does not. This asymmetry is exactly what played out in March 2025, when the widely used tj-actions/changed-files GitHub Action was compromised: attackers modified the action to dump CI runner memory into build logs, exposing secrets across an estimated 23,000 repositories that referenced it, in many cases via a floating version tag rather than a pinned commit SHA. Nobody reviewed new source code. They just kept using an action they trusted, and the pipeline betrayed them.
How did a single CI vendor breach cascade into thousands of unrelated companies?
Because the CI vendor was itself a chokepoint for thousands of companies' publishing credentials. On January 4, 2023, CircleCI disclosed that an engineer's laptop had been infected with malware capable of stealing session cookies, letting an attacker bypass two-factor authentication and access CircleCI's systems. Because CircleCI stored and processed customer secrets — API keys, npm tokens, cloud credentials — to run builds and deployments, the company had to tell every customer to rotate every secret used in a CircleCI pipeline. The breach itself compromised one vendor. The remediation burden landed on every team that had ever wired a publishing token into that vendor's environment variables.
Can a backdoor really make it into a release without touching the public source repository?
Yes, and the xz utils incident in March 2024 is the clearest proof. CVE-2024-3094 was a backdoor discovered by Microsoft engineer Andres Freund on March 29, 2024, planted by a contributor ("Jia Tan") who had spent roughly two years building trust and social-engineering their way into co-maintainer status on the widely used xz compression library. The malicious code was not fully visible in the Git history — it was assembled from obfuscated test files and injected specifically during the configure/make build process that produces release tarballs, the exact stage most CI pipelines automate and few teams audit line-by-line. Had it not been caught, it would have shipped a working SSH backdoor into essentially every major Linux distribution's build pipeline.
Do dependency confusion attacks still work against modern CI systems?
Yes — and the PyTorch incident from December 2022 shows how a misconfigured internal pipeline can be turned into a public attack surface. Between December 25 and December 30, 2022, attackers uploaded a malicious package called torchtriton to the public PyPI registry, matching the name of an internal dependency that PyTorch's nightly build pipeline pulled by name without pinning it to a private index. Because PyPI resolution defaulted to the public registry, PyTorch's own CI infrastructure fetched and executed the attacker's package, which exfiltrated SSH keys, .gitconfig data, and system information from every machine that ran the nightly build. The vulnerability wasn't in PyTorch's code — it was in how their build pipeline resolved and trusted package names.
Why do stolen publishing tokens still cause damage years after credential leaks are supposedly "solved"?
Because most registries have historically treated long-lived API tokens as an acceptable default, and one leaked token is often enough to publish a malicious version under a trusted name. The Ledger ConnectKit compromise in December 2023 followed this exact script: a former Ledger employee's npm account was compromised via a phishing attack, giving the attacker publishing rights to a JavaScript library integrated into numerous cryptocurrency dapps. The attacker pushed a malicious version that silently redirected wallet transactions, and because ConnectKit was pulled automatically into downstream builds via CI, the compromise propagated to end users of multiple unrelated crypto platforms within hours. This is the same underlying failure mode as the 2021 ua-parser-js and 2018 event-stream incidents: whoever holds the publish token owns every downstream consumer's next build.
Is this actually getting worse, or does it just feel that way?
It's getting worse, measurably. Sonatype's annual State of the Software Supply Chain research has tracked a multi-year climb in malicious packages uploaded to open source registries, moving from tens of thousands per year earlier in the decade to hundreds of thousands annually by 2023–2024 — the majority intentionally malicious rather than accidentally vulnerable. Meanwhile, registries themselves have had to react structurally: npm began requiring two-factor authentication for maintainers of high-download packages starting in 2022, and PyPI rolled out "Trusted Publishing" via OIDC in 2023 specifically to let projects publish from CI without long-lived API tokens at all. The fact that both major registries had to redesign their publishing trust model in the same two-year window is a strong signal that the pipeline, not just the code, had become the weak point.
How Safeguard Helps
Safeguard treats the publishing pipeline as a first-class part of the attack surface, not an implicit trust boundary that gets audited once and forgotten. In practice, that means:
- Pipeline provenance verification: Safeguard validates that published artifacts actually correspond to the source commit and workflow run that claims to have produced them, using SLSA-aligned provenance attestations and Sigstore/cosign signing, so a tampered build step or a poisoned action can't silently produce a different artifact than the one that was reviewed.
- Credential scoping and monitoring: instead of long-lived, broadly scoped
NPM_TOKENorPYPI_API_TOKENsecrets sitting in CI environments indefinitely, Safeguard helps teams move to short-lived, OIDC-based publishing credentials and flags workflows still relying on static tokens as a prioritized remediation item. - Dependency and action pinning enforcement: Safeguard flags GitHub Actions and CI dependencies referenced by floating tags or
@maininstead of pinned commit SHAs — the exact gaptj-actions/changed-filesexploited — and surfaces when a pinned action's underlying commit has changed ownership or maintainer activity in ways that suggest account takeover risk. - Anomalous publish detection: Safeguard baselines normal publishing behavior for a project (cadence, file sizes, build duration, network calls during build) and alerts when a release deviates — the kind of signal that would have flagged the Ultralytics cache-poisoning build or the torchtriton dependency confusion pull before it reached production consumers.
- Registry-side verification for consumers: on the consuming side, Safeguard checks that packages pulled into your builds match verified provenance and haven't been yanked, re-published, or altered post-hoc, closing the loop for teams downstream of any given compromised package.
The throughline across SolarWinds, CircleCI, xz utils, torchtriton, Ledger ConnectKit, and tj-actions is the same: the attackers didn't need to beat code review, because code review was never watching the pipeline. Securing what you ship means securing the machinery that ships it — and that has to include the CI/CD workflows that turn a git commit into a published, installable artifact.