pip install Is the New Attack Surface: Three Lessons from the LiteLLM Incident for AI Founders

TL;DR A single pip install litellm could send your SSH keys, cloud credentials, and every API key to an attacker. LiteLLM, with 97 million monthly downloads, was backdoored on March 24. 500,000 machines were compromised. This post reconstructs the incident, provides a self-check prompt, and analyzes three lessons for AI founders.


Last night, scrolling through Twitter, I saw a post from Andrej Karpathy. The title said “Software horror,” and the content lived up to it: a Python package downloaded 97 million times per month had been swapped out for a backdoored version on PyPI. The moment you installed it, your SSH keys, AWS/GCP/Azure cloud credentials, Kubernetes configs, every API key in your environment variables, Git credentials, shell history, and crypto wallets were all packaged, encrypted, and sent to an attacker-controlled server.

The post got 25,000 likes and 12,000 bookmarks.

The first thing I did after reading it was have my AI agent check whether any of my own projects were affected. The result: in a dormant project’s virtual environment, it found litellm 1.82.5. A safe version, but only two minor versions away from the compromised 1.82.7. If I had happened to run pip install --upgrade that day, this post would have started very differently.

What Happened

LiteLLM is one of the most widely used Python libraries among AI developers. Its core function is providing a unified interface to call various LLM APIs: OpenAI, Anthropic, Google, Mistral, all through one endpoint. Over 40,000 GitHub stars, with 2,000+ downstream packages depending on it.

On March 24, 2026 at UTC 10:39, a hacker group called TeamPCP used stolen PyPI publishing tokens to push two malicious versions: LiteLLM 1.82.7 and 1.82.8. These versions never went through GitHub code review. They bypassed the normal release process and were uploaded directly to PyPI.

The malicious versions contained a file called litellm_init.pth. .pth is an obscure Python mechanism: .pth files placed in the site-packages directory execute automatically every time the Python interpreter starts. No need to import litellm, no need to call any function. As long as the package existed in your virtual environment, every Flask app, Jupyter notebook, and pytest run in that same environment would trigger the malicious code.

The payload had three stages. Stage one harvested credentials: SSH, cloud platforms, K8s, databases, crypto wallets, and .env files, encrypting them with AES-256 and exfiltrating through a spoofed domain. Stage two detected Kubernetes environments and, if present, used ServiceAccount tokens to deploy privileged pods on every node for lateral movement. Stage three installed a systemd service as a persistent backdoor, continuously polling a remote server for additional malicious payloads.

The malicious versions were live on PyPI for approximately 5.5 hours.

The discovery was deeply ironic. Callum McMahon from FutureSearch was using an MCP plugin inside Cursor, which pulled in litellm as a transitive dependency. The malicious .pth file fired on every Python startup, spawning child processes that triggered the same .pth, creating an exponential fork bomb that crashed the machine by exhausting all available RAM. Karpathy’s take: “If the attacker didn’t vibe code this attack it could have been undetected for many days or weeks.” The attacker’s AI-written malware was too sloppy, and that sloppiness saved everyone.

The Attack Chain: Security Tools Became the Entry Point

The attack path deserves even more attention than the payload. This was not an isolated incident. It was a carefully designed triple jump.

On March 19, TeamPCP compromised Trivy, an open-source vulnerability scanner used by thousands of enterprises. On March 23, they used credentials stolen from Trivy to compromise Checkmarx’s KICS GitHub Action (another code auditing tool). On March 24, because LiteLLM’s CI/CD pipeline used the now-poisoned Trivy, the attackers extracted the PyPI publishing token from it and pushed the backdoored versions directly to PyPI.

The security scanner itself became the attack vector. Wiz security researcher Gal Nagli put it plainly: “The open-source supply chain is experiencing cascading collapse. Compromised credentials become ammunition for the next attack.”

According to vxunderground (a security research organization in direct contact with the attackers), the LiteLLM compromise alone resulted in approximately 500,000 machines having their credentials stolen, totaling roughly 300GB of data. TeamPCP claims to be actively using these credentials to extort multiple multi-billion-dollar companies.

Their message: “TeamPCP is here to stay. Long live the supply chain.”

Why AI Founders Should Be Especially Worried

A traditional web project’s .env file might contain a database password and one or two third-party API keys. An AI project’s .env file looks like this:

OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
GOOGLE_AI_API_KEY=AIza...
AZURE_OPENAI_KEY=...
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
PINECONE_API_KEY=...
LANGCHAIN_API_KEY=...

A typical AI startup project has three to five times the API key density of a traditional project in its .env file.

LiteLLM’s entire design purpose is to serve as a unified LLM API gateway. Its reason for existence is to centrally manage all your model API keys. The attackers’ choice of target was surgical: compromise one package, harvest every credential a company uses to call every LLM provider.

There’s also a transmission path that’s easy to overlook: transitive dependencies. You might never have manually run pip install litellm, but DSPy, MLflow, Open Interpreter, or some MCP plugin in Cursor installed it for you. LiteLLM is listed as a dependency by over 2,000 packages. The browser-use project officially confirmed on the day of the incident that their v0.12.3 was affected.

Check Now: A Prompt for Your AI Agent

If you’re using Claude Code, Cursor, ChatGPT, or any AI coding assistant, you can copy the prompt below directly and let it run the check for you:

I need you to check whether this machine was affected by the LiteLLM PyPI
supply chain attack on March 24, 2026 (versions 1.82.7 / 1.82.8).
Execute each step and report results:

1. Check litellm versions across global and all virtual environments:
   - Run pip show litellm and pip3 show litellm
   - Search all .venv and venv directories:
     find ~ -path "*/site-packages/litellm" -type d 2>/dev/null
   - Check version numbers for each instance found

2. Search for malicious .pth files:
   - find ~ -name "litellm_init.pth" 2>/dev/null
   - Check all Python site-packages directories for suspicious .pth files

3. Check for persistence backdoor:
   - Does ~/.config/sysmon/sysmon.py exist?
   - systemctl status sysmon (Linux) or launchctl list | grep sysmon (macOS)
   - Any abnormal background Python processes?

4. If in a Kubernetes environment:
   - Check kube-system namespace for node-setup-* anomalous pods

5. Scan dependency tree for indirect references:
   - Check all requirements.txt, pyproject.toml, Pipfile files
   - List all packages that directly or indirectly depend on litellm

6. Assess API key exposure surface:
   - List .env file locations (do not output contents)
   - Count environment variables containing KEY, TOKEN, SECRET, PASSWORD

Give a clear "safe / at risk" verdict for each step, then provide an
overall assessment and recommended next actions.

If the check reveals version 1.82.7 or 1.82.8, assume all credentials have been compromised and act immediately:

  1. Rotate all credentials: every API key, database password, and cloud credential in .env
  2. Remove malicious files: delete litellm_init.pth, uninstall the affected version
  3. Check persistence: remove ~/.config/sysmon/ directory and the sysmon service
  4. Audit K8s: if in a Kubernetes environment, find and remove node-setup-* pods
  5. Pin versions: lock litellm==1.82.6 or an earlier clean version in requirements.txt

Lesson One: Your Dependency Tree Is Your Attack Surface

The most unsettling part of this incident is not that LiteLLM was compromised. It’s the transmission path.

You installed dspy. dspy depends on litellm>=1.64.0. pip resolves to the latest compatible version. If that moment happened to fall between March 24 UTC 10:39 and 16:00, you were compromised. You didn’t even know litellm existed in your environment.

This problem is especially severe in the AI space. AI projects have dependency trees that are consistently deeper and wider than traditional web projects. A typical LangChain project easily has hundreds of transitive dependencies. Every single one is a trust decision, and most people never realized they were making it.

On Twitter, there was a fictional but precise post written from the perspective of a VP of Platform Security at a Series C startup. He described how his team had pinned all dependency versions two years ago, but someone unpinned litellm in February to use a new feature. One line changed in requirements.txt. No code review. Then he approved a $340,000 emergency credential rotation project. The scenario is fictional, but the decision chain it describes happens at every fast-moving startup.

Practical steps for founders: run pip-audit to scan for known vulnerabilities, use poetry.lock or uv.lock to pin version hashes, add dependency security checks to your CI/CD pipeline. These tools have always existed. Most AI startup teams have simply never used them.

A more radical approach is to reduce the number of dependencies altogether. Which leads to the second lesson.

Lesson Two: Karpathy’s “Yoink” Pattern

At the end of his 25,000-like post, Karpathy wrote:

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.

“Yoink” is slang for snatching something. Karpathy uses it to describe a new dependency management strategy: for external libraries with simple enough functionality, skip pip install and have an LLM generate equivalent implementation code directly inside your project.

This idea goes beyond security patching. It represents a more fundamental shift in architectural philosophy.

Traditional software engineering assumes that code reuse happens through shared libraries, and pip install is the default way to acquire functionality. This assumption rests on two premises. First, writing code is expensive, so use existing code when possible. Second, shared libraries are community-reviewed and quality-assured.

Both premises are eroding in the LLM era.

The cost of writing code is plummeting. Having Claude generate an HTTP retry handler, a YAML parser, or a simple rate limiter takes about 30 seconds and a few hundred tokens. By contrast, pulling in an external library means a new maintainer trust chain, a potentially deep transitive dependency tree, and an attack surface that could be compromised in the future.

The community review assumption was shattered by this incident. LiteLLM had 40,000 GitHub stars, SOC2 certification (audited by Delve), and a complete CI/CD pipeline. None of that prevented malicious versions from being uploaded to PyPI, bypassing every security mechanism. The attacker had the PyPI publishing token. Code review, PR approvals, CI checks — all rendered irrelevant. The attack happened one layer below where everyone was looking.

While maintaining my own AI-driven personal assistant system, I have over a dozen Python scripts in the scripts directory. After yesterday’s audit, I realized some lightweight dependencies could easily be replaced with LLM-generated local implementations. Not every dependency is a candidate for yoinking. Complex cryptographic libraries, database drivers, and components requiring ongoing security updates should obviously stick with formally maintained packages. But libraries imported “just to save writing 20 lines of code” deserve re-evaluation.

This is a signal of a larger trend. When code generation costs approach zero, the “dependencies are bricks” metaphor breaks down. Every brick could be a Trojan horse. If you can make a fully transparent brick in 30 seconds, why take one from a supply chain you cannot verify?

Lesson Three: API Key Management Can’t Stay in .env Files

This incident exposed a structural blind spot in AI startups.

Consider this scenario. Your AI startup has a Python virtual environment with LiteLLM installed for model routing. The same environment’s .env file holds API keys for OpenAI, Anthropic, and Google, plus Stripe payment keys and database credentials. All of these credentials exist in plaintext within the same process-accessible space. A single compromised dependency can read them all at once.

This is an architectural problem, not a “just be more careful” problem.

Yage (from the AI Builder Space community) noted in his analysis on the same day that 1Password had launched Unified Access the month before, placing human users, AI agents, and machine identities into a single credential governance framework. The emergence of this product category is no coincidence. As AI agents increasingly act on behalf of humans, agent credentials need the same rigorous management as human credentials: least privilege, regular rotation, isolated scoping.

Early-stage teams might not need enterprise-grade secrets managers, but several directions are worth pursuing now. Migrate API keys from .env files to system keychains or lightweight secrets managers (even something like dotenvx). Use separate keys for each service and each environment. Set spending limits and alerts — most API providers support this. Rotate credentials on a regular schedule.

In the “ship first, secure later” rhythm of startup life, these are usually the first things skipped. The LiteLLM incident is a reminder: the cost of skipping could mean waking up on a random Tuesday to find every credential in someone else’s hands.

In Closing

After auditing my own projects yesterday, I did three things. Added export LITELLM_TELEMETRY=False to my global shell config to disable telemetry on all litellm instances. Deleted the .venv of that dormant project entirely. Ran a dependency audit on my toolchain and flagged every lightweight dependency that could be replaced with LLM-generated code.

In this incident, LiteLLM’s maintainers were victims too. Their PyPI publishing token was stolen through a poisoned Trivy security scanner. The real problem lies in the trust chain itself.

AI toolchains have made building an order of magnitude faster. But part of that speed is borrowed: behind every pip install is an invisible chain of trust, and the moment any link in that chain breaks, speed instantly becomes liability. After the LiteLLM incident, I started to genuinely understand what Karpathy meant by yoink: when an LLM can generate equivalent code in 30 seconds, every dependency you choose not to add is one less risk exposure beyond your control. For AI founders, this might be a seriously underestimated competitive advantage.


Sources

If you found this helpful, consider buying me a coffee to support more content like this.

Buy me a coffee