
There's an assumption baked into almost every CI/CD pipeline on the planet: that a version tag means what it says. tj-actions/changed-files@v35 should always point to the same code it pointed to yesterday. That assumption held for years — right up until March 14, 2025, when someone proved it doesn't have to. The tag that lied. tj-actions/changed-files is a small, unglamorous GitHub Action used by more than 23,000 repositories to detect which files changed in a pull request. On March 14, 2025, an attacker who had compromised a personal access token belonging to the project's automation bot pushed a malicious commit, then retroactively rewrote nearly every version tag — v35, v44, v46, all of them — to point at it ( GitHub Advisory Database ). Anyone whose workflow referenced the action by tag, which is the normal, recommended way to use it, was suddenly running attacker-controlled code without changing a single line of their own configuration. \ The payload itself was almost quaint by modern standards: a script that dumped the CI runner's memory and printed any secrets it found straight into the build logs ( Wiz ). On public repositories, that meant cloud access keys, npm tokens, and private RSA keys sitting in plain text for anyone who knew where to look. Researchers later traced a plausible root cause: a separate, smaller GitHub Action called reviewdog/action-setup had been compromised days earlier, leaking the credentials that let the attacker reach tj-actions in the first place — a chain of trust running three actions deep before it touched a single line of an actual victim's code. \ What makes this attack worth dwelling on isn't its sophistication. It's how ordinary the failure was: a transitive dependency of a transitive dependency, compromised through a stolen token, exploiting a mutability assumption almost nobody questions. CISA issued a same-week advisory telling organizations to audit every workflow run between March 12 and March 15 and rotate anything that might have touched the compromised action ( CISA ). \ The fix the security community converged on — pin actions to immutable commit SHAs, not floating tags — had been best practice advice for years. Almost nobody had done it. Then the worm arrived. If tj-actions was a single, contained breach, what happened to the npm ecosystem six months later showed what happens when a similar idea gets automated and let loose. \ In late August 2025, attackers compromised a maintainer's GitHub account in what became known as the s1ngularity/Nx incident, stealing npm publishing tokens in the process. By September 15, those stolen tokens had been used to publish malicious versions of legitimate packages — including @ctrl/tinycolor , a color-manipulation library with more than two million weekly downloads. \ What made this different from a typical credential-theft incident was the payload's design: once installed, it scanned the host for any other npm tokens it could find, and automatically published trojanized versions of whatever packages those tokens controlled, too ( Wiz ). \ Security researchers named it Shai-Hulud, after the sandworms in Dune , and for good reason — it burrowed and multiplied without further human direction. Within days, it had compromised more than 500 packages. Each infected package harvested cloud credentials using the open-source secret-scanning tool TruffleHog, then published the stolen data to a public GitHub repository — also named Shai-Hulud — that anyone, including the attacker, could read ( StepSecurity ). \ Trend Micro's research team summarized the architectural shift plainly: "Shai-Hulud distinguishes itself through its ability to self-replicate within the NPM ecosystem" ( Trend Micro ). Maintainer's GitHub Stolen npm Malicious package account compromised → publish token → version published (s1ngularity/Nx, Aug) stolen (Shai-Hulud, Sept 15) │ ┌───────────────────────────┘ ▼ Package installed on a new machine Worm scans for *other* npm tokens present on that machine │ ▼ Publishes trojanized versions of whatever packages those tokens control (no further attacker action required) \ By November 2025, a second wave — Shai-Hulud 2.0 — had widened the blast radius dramatically, eventually touching more than 25,000 GitHub repositories across roughly 350 unique accounts and shifting execution from the post-install phase to pre-install, which meant the malicious code ran even before a developer's own dependency-resolution checks had a chance to flag anything unusual ( Unit 42 ). \ Researchers at Unit 42 assessed with moderate confidence that the attackers had used an LLM to help write the malicious bash script, based on stray comments and emoji left in the code — a strange, almost mundane detail that says a lot about how routine AI-assisted malware development has already become. 2026: The window keeps shrinking. If 2025 was the year supply-chain attacks learned to self-replicate, early 2026 has been the year they learned to move fast enough that detection windows measured in days became windows measured in minutes. \ In March 2026, the LiteLLM package on PyPI was compromised in an incident serious enough that it potentially exposed 500,000 credentials, including API keys for Meta, OpenAI, and Anthropic — Meta reportedly froze parts of its AI data work while assessing the exposure ( TheNextWeb ). \ The following month, a Bitwarden CLI package on npm was hijacked for ninety minutes with a payload purpose-built to harvest credentials specifically from AI coding tools — Claude Code, Cursor, Codex CLI, and Aider. Days after that, PyTorch Lightning was compromised for forty-two minutes by a "Mini Shai-Hulud" variant before it was caught. TheNextWeb's reporting on the pattern landed on the part that should worry every CI/CD operator: "the common thread is speed" ( TheNextWeb ). \ Forty-two minutes is not a window most security teams can realistically detect and respond inside. It's the time it takes to get coffee and come back. These are not slow-burn espionage campaigns designed to sit undetected for months; they're smash-and-grab insertions engineered around the brutal arithmetic of automated dependency resolution — install at the wrong minute, and the compromised version is already on your machine before anyone notices anything changed. The numbers behind the trend. None of this is an isolated cluster of bad luck. Sonatype's tracking of malicious packages across open-source registries shows the trajectory clearly: from roughly 55,000 confirmed malicious packages in 2022 to 454,600 by 2025, with the two biggest single-year jumps lining up almost exactly with the release of GPT-4 and the mainstream arrival of agentic coding assistants ( The Hacker News ). The same reporting captured just how far the skill floor for this kind of attack has dropped, noting that "an amateur in Algeria built ransomware that hit 85 targets in his first month" — using nothing but an AI coding assistant and persistence ( The Hacker News ). \ Mandiant's M-Trends 2026 data adds the other half of the picture: average time-to-exploit for newly disclosed vulnerabilities has collapsed from over 700 days in 2020 to roughly 44 days in 2025, and more than a quarter of all CVEs are now weaponized within 24 hours of public disclosure. Patch management built around weekly or monthly cycles is now structurally too slow for a meaningful share of the threat landscape it's supposed to cover. What actually changes the outcome here Strip away the specific incident names, and a short list of concrete practices separates the organizations that weathered these incidents from the ones that spent weeks rotating secrets: Pin dependencies and Actions to immutable commit SHAs, not mutable version tags. This single habit would have prevented the entire tj-actions incident regardless of how the upstream compromise happened. Tags can be rewritten. Commit hashes cannot. Treat npm, PyPI, and GitHub tokens as production credentials, not developer conveniences. Shai-Hulud's entire self-propagation mechanism depended on finding more publish tokens sitting on infected machines. Scoped, short-lived, audited tokens break that chain before it starts. Assume your detection window is now measured in minutes, not days. A forty-two-minute compromise window means automated, pre-merge dependency scanning and runtime monitoring on CI runners aren't optional hardening anymore — they're the only realistic chance of catching an incident before it spreads. Audit what's allowed to read your CI/CD secrets at all. TruffleHog-style secret scanning is a legitimate defensive tool; it's also, as Shai-Hulud demonstrated, an extremely efficient weapon when an attacker gets to run it on your infrastructure instead of you. \ The uncomfortable throughline across tj-actions, Shai-Hulud, and the rapid-fire 2026 compromises is that none of them required a novel zero-day. Everyone exploited a process assumption — that tags don't change, that a maintainer's credentials are theirs alone, that a forty-two-minute window is too short to matter. Those assumptions were reasonable once. They are now the most reliable attack surface in software. \
View original source — Hacker Noon ↗


