From tj-actions to LiteLLM to MCP: supply chain compromise now operates at infrastructure scale
Eighteen months of supply chain attacks against AI infrastructure reveal a structural pattern: the build pipeline, the package registry and the runtime protocol all share the same trust model failure.
Three supply chain attack campaigns between March 2025 and March 2026 compromised three entirely different layers of modern software infrastructure. The tj-actions/changed-files attack rewrote Git tags to redirect 23,000 repositories to a malicious commit. The LiteLLM backdoor reached 46,996 downloads in under an hour by compromising the project's own vulnerability scanner. And the MCP ecosystem opened a new category entirely: runtime tool registries where AI agents connect to servers controlled by unknown publishers, pulling executable instructions at inference time.
These are not three unrelated incidents. They are the same structural failure expressed at three different layers of the stack: the build pipeline, the package registry and the runtime protocol. Each exploits mutable references, self-asserted identity and the absence of content-addressable verification. Each targets the implicit trust that developers place in infrastructure they did not build and cannot inspect. And each scales in a way that individual vulnerability exploitation does not. A single compromised tag, a single poisoned package version, a single malicious tool description can propagate through thousands of downstream consumers before anyone notices.
The question is no longer whether supply chain attacks work. It is whether the architecture of modern software distribution can be fixed without breaking the convenience that made it dominant.
The build pipeline: where tags are promises
The tj-actions attack, first detected by StepSecurity's Harden-Runner in March 2025, remains the clearest demonstration of what happens when mutable references serve as trust anchors. The attacker gained access to the tj-actions/changed-files repository through a chain that started with a SpotBugs repository exploit in November 2024, moved through reviewdog and finally reached the target. Once inside, they overwrote every version tag from v1.0.0 through v45.0.7, redirecting all of them to a single commit that dumped CI runner memory and logged secrets in plaintext.
Unit 42's investigation traced the full chain. Wiz's analysis confirmed the scope. The numbers are difficult to argue with: 23,000 repositories affected by a technique that required no zero-day, no novel exploit and no sophisticated tooling. Git tags are mutable by default. The attacker simply overwrote them.
The academic data makes this worse. Pan et al.'s study across 320,000 repositories found that 94.93% of GitHub Actions references use tags rather than commit SHAs. That means nearly the entire CI/CD ecosystem pins its trust to pointers that any repository maintainer (or anyone who compromises a maintainer) can silently redirect. The remaining 5% who pin to SHAs are safe. Everyone else is relying on a gentleman's agreement that tags will not be rewritten.
The fix is known: pin to commit SHAs. The adoption rate tells you everything about the gap between knowing a fix and deploying it. A year after the tj-actions disclosure, the overwhelming majority of workflows still reference tags. Convenience wins by default. Security requires active effort. That asymmetry is the entire problem.
The package registry: 46 minutes is enough
If the build pipeline attack exploits mutable references, the LiteLLM backdoor exploits mutable trust in package registries. LiteLLM is the universal LLM proxy: 95 million monthly downloads, used to route API calls across every major model provider. In March 2026, versions 1.82.7 and 1.82.8 were uploaded to PyPI with a backdoor that harvested SSH keys, cloud credentials, Kubernetes secrets across all namespaces, crypto wallets and .env files. Version 1.82.8 used a .pth file that executed during pip install itself, before any application code ran.
PyPI quarantined the packages after 46 minutes. By then, 46,996 downloads had occurred and 23,142 environments were compromised on installation alone.
The attack vector was recursive: a compromised Trivy vulnerability scanner delivered the backdoor to LiteLLM's own CI pipeline. The project's security tooling was the entry point. That is not irony. It is the logical endpoint of supply chain trust chains: if you trust your scanner and your scanner is compromised, your build artefacts are compromised. The chain of custody has no independent verification at any link.
The campaign behind this, attributed to TeamPCP, spans five ecosystems: GitHub Actions, Docker Hub, npm, OpenVSX and PyPI. It includes a geopolitically targeted wiper that destroys Iranian Kubernetes clusters. This is not opportunistic credential theft by a lone actor. It is systematic infrastructure compromise with lateral movement capabilities built into the payload from the start.
The dependency graph made the blast radius predictable before the attack even happened. 88% of the 2,337 packages that depend on LiteLLM had version specifiers (wildcards, compatible-release operators, greater-than constraints) that would have resolved to the compromised versions during the attack window. The default way to specify a dependency in Python is the vulnerable way.
The runtime protocol: trust at inference time
Build pipelines and package registries are at least understood attack surfaces, even if they remain poorly defended. MCP introduced something new: a supply chain attack surface that operates at runtime, after the software is built and deployed, at the moment an AI agent decides to call an external tool.
The Model Context Protocol arrived in November 2024 as Anthropic's proposal for a standard interface between language models and external tools. Within months, thousands of MCP servers appeared in community registries. Wiz found that roughly 100 of the 3,500 servers listed on popular registries pointed to non-existent GitHub repositories: ghost packages waiting for anyone to claim the namespace and serve arbitrary code to every agent that had previously configured that tool.
Invariant Labs demonstrated tool poisoning attacks where hidden instructions embedded in MCP tool descriptions caused agents to exfiltrate SSH keys and configuration files without user awareness. The attack required no code execution in the traditional sense. The tool description itself was the payload, processed by the language model as trusted context.
The rug pull variant is worse. An MCP server can change its tool descriptions after a user has approved access. What looked like a harmless search tool on day one can silently become a credential exfiltration tool on day seven. The approval model is point-in-time. The risk is continuous.
This is the architectural novelty. Build pipelines and package registries at least have the concept of a fixed artefact: a commit, a tarball, a wheel file. MCP servers are live services. Their behaviour can change between calls. There is no artefact to pin, no hash to verify, no immutable reference to audit. The trust model is pure reputation. The registries that list these servers perform no verification of publisher identity.
The shared failure
Strip the implementation details and the pattern is identical across all three layers:
Mutable references. Git tags can be overwritten. PyPI version specifiers resolve to whatever the latest matching upload is. MCP tool descriptions can change after approval. In each case, the identifier that the consumer trusts points to something the attacker can silently replace.
Self-asserted identity. A GitHub Actions publisher is whoever controls the repository. A PyPI publisher is whoever holds the API token. An MCP server publisher is whoever registers the name first. None of these systems verify identity through an independent chain of trust. Compromise one credential and you inherit the publisher's reputation.
No content-addressable verification. Git commit SHAs exist but 95% of the ecosystem does not use them. PyPI supports hash verification but the default tooling does not enforce it. MCP has no verification mechanism at all. The secure option is available in theory and unused in practice.
This is the same failure that I wrote about in March when examining the shared authentication model across Git tags, package registries and extension marketplaces. The specifics have changed. The market for AI tools has added a new layer. But the structural problem has not moved.
Why the fixes do not propagate
The solutions are not mysterious. Pin to immutable references. Verify publisher identity through out-of-band mechanisms. Use content-addressable storage so that artefacts cannot be silently replaced. Sigstore exists. SLSA provenance exists. Commit SHA pinning exists. These are not theoretical proposals. They are deployed systems with real adoption among security-conscious teams.
The problem is that "security-conscious teams" is a small minority. The default behaviour in every ecosystem is the insecure one. npm install fetches the latest matching version. uses: action@v3 follows a mutable tag. Connecting an MCP server requires clicking one approval prompt. The path of least resistance is the path of maximum exposure.
Changing the defaults is politically difficult because it breaks workflows. Requiring commit SHA pinning would make GitHub Actions harder to use. Enforcing hash verification for every pip install would break development environments. Mandating cryptographic server identity for MCP would slow the ecosystem's growth. These are real costs and they fall on every user, not just the ones under attack.
The result is a coordination problem. Individual teams can harden their own supply chains, but the ecosystem-level risk remains because the majority does not. The attacker only needs to find one consumer who has not pinned, one dependency that resolves to the latest version, one agent that has not revoked an old MCP approval. In a target-rich environment, selectivity is unnecessary.
What scales
The uncomfortable observation is that supply chain attacks scale better than supply chain defences. An attacker who compromises one popular package reaches every downstream consumer automatically. A defender who pins one dependency has secured exactly one dependency. The asymmetry is not a bug in any particular tool or registry. It is a property of dependency graphs themselves: influence propagates downstream by default. Isolation requires active effort at every node.
TeamPCP's campaign across five ecosystems is the proof of concept for what this looks like when a capable actor decides to exploit the pattern systematically. The tj-actions attack was a single chain through three repositories. The LiteLLM attack was a single poisoned security tool. The MCP ecosystem has not yet seen a coordinated campaign at that scale, but the attack surface is there, listed in public registries, waiting.
The history of infrastructure security suggests that trust models do not get fixed until the cost of not fixing them becomes unbearable. TLS adoption was glacial until browsers started marking HTTP sites as insecure. Code signing on mobile platforms was resisted until malware in app stores became a headline problem. Supply chain verification is at the stage where the attacks are visible but the cost has not yet been distributed widely enough to force the defaults to change.
When it does change, it will not be because the security community wrote persuasive blog posts. It will be because a supply chain attack caused enough damage to enough organisations that the platform operators had no choice but to break backward compatibility. The question is how much damage that takes and which ecosystem breaks first.
Newsletter
One email a week. Security research, engineering deep-dives and AI security insights - written for practitioners. No noise.