On 21 October 2026, for roughly two hours, anyone who ran pip install litellm got version 1.107.0. That release carried a credential stealer and a remote-code loader that checks in every hour. Datadog Security Labs puts the PyPI upload at 11:39 UTC and PyPI's quarantine at 13:20 UTC. The Docker images were pulled at 13:53 UTC. LiteLLM's own pipeline built and signed the release, so the signature was valid (CSO Online).

For anyone running AI agents, the useful part is how the attacker reached the pipeline. LiteLLM's attack-chain write-up of 23 October traces it to a key left in an agent configuration file, and to an agent platform that held a human owner's GitHub token. Both are common ways of wiring agents into a company.

What happened

According to LiteLLM's timeline:

  • 27 May: an opencode.json file was committed to a public repository. It held the master key for LiteLLM's Agent Platform, an internal alpha service that runs cloud agents which open pull requests. The commit was force-pushed over, but "key revocation did not occur for the master key."
  • 10 October: the attacker used that key against the Agent Platform API. One request to the agents endpoint returned the configuration of every managed agent, including the GitHub CLI OAuth token of an organisation owner. Within the hour, workflows pushed to internal branches sent more maintainer tokens to an outside webhook, and the attacker then deleted those workflow runs.
  • 21 October, 10:08 UTC: branch protection rejected a push. Five seconds later the owner token switched the protection off and force-pushed. The release source was pointed at a lookalike fork, Berri-AI/litellm. The real organisation is BerriAI.
  • 11:11 UTC: the production gate was approved with a second maintainer's stolen token. The wheels went out at 11:39 and the container images at 11:46.

The payload added cook.pth and sitecustomize.py to site-packages, so it runs every time Python starts, even if LiteLLM is never imported. Datadog lists what it goes after: cloud credentials, SSH keys, Git, npm and PyPI tokens, .env files, shell history, and API keys and sessions for Claude, Codex, Gemini, Cursor and Copilot. Deleting the package leaves the payload in place, and running that environment's python or pip sets it off again.

Why it happened

Nothing in this chain is exotic. It is four ordinary decisions stacked on top of each other:

  • A secret in an agent config file, in a repository. Force-pushing hides a commit from the visible history. It does not take the key back from anyone who already copied it. Only revoking the key does that.
  • An automation running on a person's token. LiteLLM says plainly: "The token in question was not associated with a service account and was not scoped down." The platform only needed to open pull requests, yet it held a credential that could rewrite branch protection across the organisation.
  • Protections the owner could switch off. If an owner can disable branch protection and force-push, branch protection does nothing against a stolen owner credential.
  • Two-person approval that one actor could satisfy. Earlier that week the release rule had been relaxed to allow "any two engineers to trigger and approve a release." With two stolen tokens, one attacker could trigger the release and approve it.

The second decision deserves the most attention. Plenty of teams wire an agent to GitHub with the token of whoever set it up, and that is often an admin. From then on, the agent's blast radius is that admin's blast radius.

The controls that break the chain

  • Give every agent its own identity. Use a GitHub App or a fine-grained token, limited to the repositories and permissions the agent actually needs, with a short expiry. Never use an organisation owner's personal CLI login.
  • Treat agent config files as secret-bearing. That means opencode.json, MCP server configs, agent settings files and .env. Keep them out of version control, scan for secrets before commit, and when one leaks, revoke first and clean up history second.
  • Make release protections binding on admins too. Remove bypass rights on release repositories, and alert on any change to protection rules and any force-push to them.
  • Tie separation of duties to people, not tokens. If two approvals can come from two credentials one party holds, you have one approval. Require phishing-resistant sign-in for release approvals, and never weaken the rule quietly.
  • Pin downstream installs. Install from lockfiles with hashes, and don't let builds or developer machines float to latest. A short waiting period before brand-new versions are allowed would have outlasted this two-hour window.

These map directly onto existing obligations. NIS2 Article 21 lists supply chain security and access control policies among its required measures, and ISO 27001 and SOC 2 both expect privileged access to be restricted and reviewed. An agent running on an owner's token fails that review.

Where agent guardrails fit, and where they don't

To be precise: the attacker's calls to LiteLLM's Agent Platform API and to GitHub were server-side, and no endpoint policy would have seen them. The developer side of the chain is different. Coding agents work there every day: they commit config files, push branches, run gh and install packages. That is where allow, ask and deny verdicts belong.

  • Deny agent reads of ~/.config/gh/hosts.yml, cloud credential files and .env files outside the project. The agent should act through its own scoped identity, not borrow the developer's.
  • Deny git push --force from agents, and any gh api call that changes branch protection or rulesets. Ask before pushing to release branches.
  • Ask before staging or committing known secret-bearing config files.
  • Ask on unpinned installs and upgrades such as pip install -U litellm. Allow installs that resolve from the lockfile.
  • Audit every attempt. When the question is "which machines installed 1.107.0 between 11:39 and 13:53 UTC?", the answer should come from a log, not a survey of the engineering team.
text
# Illustrative policy intent (not product syntax)
read  ~/.config/gh/hosts.yml, ~/.aws/credentials   -> deny
exec  git push --force*                            -> deny
exec  gh api */branches/*/protection*              -> deny
exec  git push * release/*                          -> ask
exec  pip|uv|poetry install <pkg> (no pinned version) -> ask
exec  pip install -r requirements.lock --require-hashes -> allow
(anything unmatched)                                -> ask

DarkControl enforces these verdicts on the endpoint, outside the agent, for Claude Code, Codex, Cursor, Copilot, OpenCode and others. One rule covers pip, npm, yarn, pnpm, apt and brew. Anything a rule doesn't match defaults to Ask, and every attempt goes to an immutable log you can export to your SIEM. If you want to see how many of your developers' agents can read an admin's GitHub token today, the free seven-day watch-only audit shows it without blocking anything.


Book a demo