Wait a Day Before Installing New Releases: What This Week's Supply Chain Attacks Have in Common
The malicious LiteLLM release stayed on PyPI for 1 hour 44 minutes; pnpm and GitHub both default to a one-day cooldown for new versions, and this is why
Several supply chain attacks hit this week, each with a different method. After reading through them, I'm less sure about the habit of always running the latest version.
What happened this week
Let's start with LiteLLM. The attacker stole the GitHub credentials of 3 engineers. Using one of the accounts with admin rights, they turned off the protection that restricts who can change code. Then they pointed the release system's source location at a fake repository with a very similar name. So the malicious version was built and published through the official process, and the public 1.107.0 source on GitHub is clean.
The malicious version drops files into the Python environment. Other Python programs in that same environment can trigger it when they start, even if they never use LiteLLM. What was stolen includes cloud credentials, AI service keys, and SSH private keys.
The report has one more detail. The LiteLLM team found that Claude Code, in certain situations, put GitHub login credentials into its reply content, and that content may have been logged in internal systems. Whether this led to an actual leak is still under investigation. Still, anyone who writes code with AI agents should probably check which logs their tokens end up in.
OVHcloud's CLI was also hit. Five releases from v0.11.0 to v0.15.0, published on October 19, were infected with credential-stealing code. The attacker got in through a compromised developer GitHub account and replaced the release files. The WordPress SEO plugin Rank Math PRO had a backdoor in version 3.0.123. On the Arch Linux AUR, someone uploaded packages with names close to yay and paru. If you installed one, the attacker could run remote commands as root.
There's also a Python issue that isn't an attack, but is easy to miss. A certificate private key for eMudhra's emSign had a problem, so Python rushed out 3.15.1, 3.14.9, and 3.13.17 on October 21, updating the bundled pip to 26.2.2. The catch is that updating Python doesn't update your existing virtual environments. The pip, certifi, and uv inside each venv have to be upgraded separately, and containers need to be rebuilt with the layer cache turned off. Updating the OS certificate store isn't enough either, because pip reads the certifi it bundles itself.
1 hour 44 minutes
Back to LiteLLM. The poisoned PyPI version went live at 19:39 Taiwan time on October 21 and was taken down at 21:23, 1 hour 44 minutes later. The Docker Hub images took longer to clean up, finishing at 22:20.
The pnpm docs have a line that fits here: most malicious versions are found and removed from the registry within an hour. The minimumReleaseAge setting is built for exactly this. A newly published version won't be installed until it has been out for the number of minutes you set, and that includes transitive dependencies. The setting was added in v10.16.0. Starting with v11, the default is 1440, which is one day.
GitHub's NPM package firewall, launched this week in public preview (currently only for GitHub Enterprise Cloud), uses the same idea. It calls it a version cooldown. The default is 1 day, and you can set anything from 1 to 365 days. It also checks whether a new version is missing the SLSA provenance attestation the previous version had, or whether it adds preinstall or postinstall scripts that run at install time.
I don't think it's a coincidence that both default to one day. Most projects lose almost nothing by getting a new version a day later. For this LiteLLM incident at least, a one-day cooldown would have been enough to avoid the malicious PyPI version.
What cooldowns don't cover
A cooldown only applies at the package manager level. When the LiteLLM Docker image was swapped, anyone who pulled whatever image was tagged latest got hit, even if they never pinned 1.107.0. The latest tag won't wait a day for you. A cooldown also can't stop a misspelled package like the AUR ones. That's installing the wrong thing, not installing too fast.
On the other side, when you really need an urgent fix, such as this week's Node.js security update, you don't want the cooldown to block it. pnpm has minimumReleaseAgeExclude, which lets you exclude packages by name.
If you're on pnpm 11 or later and haven't changed any settings, the cooldown is already on. Projects still on pnpm 10 can add one line to pnpm-workspace.yaml:
minimumReleaseAge: 1440
For Python, remember to upgrade pip inside your venv too.
Sources:
- LiteLLM engineer's GitHub credentials stolen, official release process turned into a malware distribution channel (iThome, in Chinese)
- GitHub opens an NPM package firewall to block high-risk versions before installation (iThome, in Chinese)
- eMudhra certificate incident reaches Python, official emergency pip update (iThome, in Chinese)
- pnpm settings: minimumReleaseAge