pull down to refresh

The @hn bot posted this earlier, but I'm reposting because it sounds like a doozy and to add some commentary.

Here's how karpathy described it:

Simple pip install litellm was enough to exfiltrate SSH keys, AWS/GCP/Azure creds, Kubernetes configs, git credentials, env vars (all your API keys), shell history, crypto wallets, SSL private keys, CI/CD secrets, database passwords.

LiteLLM itself has 97 million downloads per month which is already terrible, but much worse, the contagion spreads to any project that depends on litellm. For example, if you did pip install dspy (which depended on litellm>=1.64.0), you'd also be pwnd. Same for any other large project that depended on litellm.

Afaict the poisoned version was up for only less than ~1 hour. The attack had a bug which led to its discovery - Callum McMahon was using an MCP plugin inside Cursor that pulled in litellm as a transitive dependency. When litellm 1.82.8 installed, their machine ran out of RAM and crashed. So if the attacker didn't vibe code this attack it could have been undetected for many days or weeks.

Supply chain attacks like this are basically the scariest thing imaginable in modern software. Every time you install any depedency you could be pulling in a poisoned package anywhere deep inside its entire depedency tree. This is especially risky with large projects that might have lots and lots of dependencies. The credentials that do get stolen in each attack can then be used to take over more accounts and compromise more packages.

Classical software engineering would have you believe that dependencies are good (we're building pyramids from bricks), but imo this has to be re-evaluated, and it's why I've been so growingly averse to them, preferring to use LLMs to "yoink" functionality when it's simple enough and possible.

It's probably evidence of my ignorance (or that what little I know about software has been learned in a bitcoin context) that I have always assumed dependency was a sort of dirty word. Something one admitted reluctantly to having like an STD.

Here's a more formal description of the attack:

At 10:52 UTC on March 24, 2026, litellm version 1.82.8 was published to PyPI. The release contains a malicious .pth file (litellm_init.pth) that executes automatically on every Python process startup when litellm is installed in the environment. No corresponding tag or release exists on the litellm GitHub repository — the package appears to have been uploaded directly to PyPI, bypassing the normal release process.

We discovered it when the package was pulled in as a transitive dependency by an MCP plugin running inside Cursor. The .pth launcher spawns a child Python process via subprocess.Popen, but because .pth files trigger on every interpreter startup, the child re-triggers the same .pth — creating an exponential fork bomb that crashed the machine. The fork bomb is actually a bug in the malware.

And what it was doing:

What the malware doesWhat the malware does

The payload operates in three stages:

Collection. A Python script harvests sensitive files from the host: SSH private keys and configs, .env files, AWS / GCP / Azure credentials, Kubernetes configs, database passwords, .gitconfig, shell history, crypto wallet files, and anything matching common secret patterns. It also runs commands to dump environment variables and query cloud metadata endpoints (IMDS, container credentials).

Exfiltration. The collected data is encrypted with a hardcoded 4096-bit RSA public key using AES-256-CBC (random session key, encrypted with the RSA key), bundled into a tar archive, and POSTed to https://models.litellm.cloud/ — a domain that is not part of legitimate litellm infrastructure.

Lateral movement and persistence. If a Kubernetes service account token is present, the malware reads all cluster secrets across all namespaces and attempts to create a privileged alpine:latest pod on every node in kube-system. Each pod mounts the host filesystem and installs a persistent backdoor at /root/.config/sysmon/sysmon.py with a systemd user service. On the local machine, it attempts the same persistence via ~/.config/sysmon/sysmon.py.