On 21 October 2026, litellm==1.107.0 on PyPI and the matching LiteLLM container images shipped files that run a credential stealer whenever Python starts. You are affected if you installed that version or pulled latest, main-latest or main-stable during the window. Isolate those hosts, rotate secrets from a clean machine, and rebuild on 1.106.2 pinned by hash or digest.
LiteLLM is an open source Python library and proxy that gives applications one interface to many LLM providers. Socket puts its downloads at about 23 million a week, and a LiteLLM proxy often holds every provider key an organisation has. The unusual detail here is in the build record: it named the source owner as Berri-AI, one hyphen away from the real BerriAI.
What happened
According to the report Upwind's researchers filed on BerriAI's GitHub (issue #47826), the payload is not in the v1.107.0 git tag. It sits in ten commits made in a lookalike repository, Berri-AI/litellm, on top of a legitimate main branch commit. BerriAI's private release workflow then built the wheels and container images from that chain. Socket reached the same view, and SafeDep stated moderate to high confidence while noting that only BerriAI's pipeline logs can confirm the cause.
All times are UTC on 21 October 2026.
| Time | Event | Source |
|---|---|---|
| 06:59 | v1.107.0 tag created; this tree is clean | SafeDep, Upwind |
| 08:56 to 10:46 | Ten commits authored as "Berri-AI" add a cook/ folder, reset version numbers, add data = "cook" to the build config and copy the folder into the Dockerfiles | Upwind, Wiz |
| 10:51 to 11:06 | Malicious container build in BerriAI's project-releaser workflow | Socket |
| 11:39:51 | litellm==1.107.0 uploaded to PyPI (seven wheels and an sdist) | Socket, Upwind |
| 11:46:30 | Socket records malware alerts | Socket |
| About 11:48 | Pulls of latest, main-latest and main-stable start returning the infected image, per Upwind; Socket's own check confirmed this at about 13:14 | Upwind, Socket |
| 12:06 | Upwind opens issue #47826 | GitHub |
| About 13:20 | PyPI quarantines 1.107.0; its history page shows "Release quarantined on Oct 21, 2026" | Wiz, PyPI |
| About 13:53 to 14:32 | Malicious Docker tags removed; Wiz gives about 13:53, BerriAI confirms removal at 14:32 | Wiz, BerriAI |
| 16:32 | BerriAI: actions taken through three engineers' GitHub accounts, apparently via stolen GitHub CLI OAuth tokens; one account triggered the release run, another approved it | BerriAI on issue #47826 |
| 17:03 | Outside probe on the issue: v1.107.0 and the floating tags return 404 on GHCR and Docker Hub; v1.106.2 still resolves | Issue #47826 |
Reports differ slightly on wheel build times and on when the Docker tags came down. None of that changes what you do.
BerriAI says it has not found how the tokens were obtained. As a temporary step it removed all but one unaffected user from its GitHub organisation and removed trusted links between its public registries and that organisation. It also says its private enterprise registry was not affected. When we checked on the evening of 21 October, neither the GitHub Advisory Database nor OSV had a record for 1.107.0, so tools that depend on those feeds may not flag it yet.
Not the March 2026 incident
In March, litellm 1.82.7 and 1.82.8 were published with malicious code. LiteLLM's security update says it believes that compromise came through the Trivy dependency in its CI scanning, and that the proxy Docker image was not affected. GitHub tracks it as GHSA-92x9-889m-jgmw, and its payload file was litellm_init.pth. October is a different version, different files, a different route and, this time, container images too. A search for the March indicator will not find this one.
Who is affected
| Artifact or setup | Status | Source |
|---|---|---|
litellm==1.107.0, any of the seven wheels (macOS, manylinux, musllinux, Windows) | Malicious; quarantined | Socket, Upwind, PyPI |
litellm-1.107.0.tar.gz | Tampered build config without the payload files, per Upwind and Socket; SafeDep lists it as malicious. Treat it as bad. | Upwind, Socket, SafeDep |
ghcr.io/berriai/litellm, litellm-database, litellm-non_root and docker.io/litellm/litellm at v1.107.0, or pulled as latest, main-latest or main-stable in the window | Malicious; tags removed | Upwind, Socket, BerriAI |
CI jobs that installed an unpinned litellm or built FROM a floating LiteLLM tag in the window | Treat the runner and its outputs as exposed | Socket |
| 1.106.2, the earlier releases Upwind checked, and 1.107.0rc1 and rc2 | Reported clean | Upwind, SafeDep |
| BerriAI's private enterprise registry | Not affected, per BerriAI | BerriAI |
Socket notes it does not certify earlier artifacts as clean, so "reported clean" means exactly that. The PyPI window was roughly 11:40 to 13:20 UTC. The floating Docker tags were exposed from about 11:48 until early afternoon.
What the startup files do
The danger is in when the code runs. Each wheel adds three files that pip places at the root of site-packages: cook.pth, which holds a single import cook line; cook.py, the encoded credential collector; and sitecustomize.py, a second entry point that starts a background loader.
Python's site module reads every .pth file in site-packages at interpreter startup and, as Python's documentation explains, executes lines that begin with import. Python also imports any module named sitecustomize automatically. So any Python process started in an affected environment runs the payload, whether or not it imports LiteLLM. Running pip starts Python too. Socket says downloading the wheel or opening it as a ZIP does not run anything. Python's docs mark import lines in .pth files as deprecated from 3.15, but older interpreters will be around for years.
Across the reports from Socket, Upwind, Wiz and SafeDep, the collector runs once in a background thread and gathers:
- cloud credentials for AWS, Azure, Google Cloud and Kubernetes, including role credentials from instance metadata, which Upwind says it validates with STS
GetCallerIdentity - SSH, GPG and TLS private keys, git and package registry tokens, and
.envfiles - AI coding tool credential files, browser password stores and shell histories
- environment variables, including those of other readable processes on Linux
- if
DATABASE_URLis set, the LiteLLM proxy's credentials and model tables, which hold the upstream provider keys
Results are encrypted and posted to www.litellm.dev. Upwind says that domain was registered on 13 October and is not the project's, which uses litellm.ai.
sitecustomize.py forks a detached process that checks a file in a GitHub account every hour and, if the file names a domain, fetches Python from it and runs it in memory. Upwind says the file had been empty since 20 October, so no second stage was seen, and the account has since been taken down. Still, an affected host could have been told to run anything. Upwind says the loader fails on Windows while the collector still runs, and Socket says some modules fail on macOS while others work. No platform is safe by default.
How to check
Start without Python. The first two steps read files with the shell, so they do not trigger the payload.
-
Look for the files and the version.
find / -path '*site-packages/cook.pth' -o -path '*site-packages/cook.py' 2>/dev/null find / -path '*site-packages/litellm-*.dist-info' -type d 2>/dev/null | grep 1.107.0 ls -la /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock 2>/dev/nullA
sitecustomize.pycan be legitimate, so compare hits with the hashes in Socket's and Upwind's indicator lists rather than flagging on name. -
Look for a running loader on Linux:
lsof /tmp/remote_code_loader.lock 2>/dev/null grep -l PRIVCOLLECT_ACTIVE /proc/*/environ 2>/dev/null ps -eo pid,ppid,cmd | awk '$2==1 && /python/'Upwind notes the marker file is written only after a successful upload and vanishes with tmp cleanup or a container restart. A missing marker proves nothing.
-
Use
pip showandpip freezeonly where step 1 came back clean. They start Python. They are fine for cleared environments, CI images you are rebuilding, and hosts you expect to be on an older version:pip show litellm | grep -i '^version' pip freeze | grep -i '^litellm==' -
Check lockfiles and history, which show what CI may have resolved during the window:
grep -rn --include='requirements*.txt' -i 'litellm==1.107.0' . grep -n -A1 'name = "litellm"' uv.lock poetry.lock 2>/dev/null | grep '1.107.0' git log -p --since=2026-10-21 -- '*.lock' 'requirements*.txt' | grep -n 'litellm.*1\.107\.0'Then search CI logs for
litellm-1.107.0in jobs that ran after 11:39 UTC, and check package caches and internal PyPI mirrors that might still hold a copy. -
Check image digests, not tags. A tag does not tell you what was pulled at 12:30 UTC.
docker images --digests | grep -i litellm docker inspect --format '{{.Name}} {{.Image}} {{.Config.Image}}' $(docker ps -q) docker image inspect --format '{{json .RepoDigests}}' ghcr.io/berriai/litellm:main-stable kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range .status.containerStatuses[*]}{.imageID}{" "}{end}{"\n"}{end}' \ | grep -E '1bbcb1443bc9|bd1aeee419ea|56b74573eed6|c00f9d92c427|7b00e91cd2fe'Socket and Upwind give the infected index digest for
ghcr.io/berriai/litellmanddocker.io/litellm/litellmassha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e, with platform digests beginningbd1aeee419ea(amd64) and56b74573eed6(arm64). Upwind listslitellm-databaseas beginningc00f9d92c427andlitellm-non_rootas7b00e91cd2fe. To look inside a container without starting Python, rundocker exec CONTAINER_ID sh -c 'find / -name cook.pth 2>/dev/null'. -
Check egress and cloud logs. Search DNS and proxy logs from 11:39 UTC for
litellm.dev, forraw.githubusercontent.com/letters9813/, and for paths ending in/codepy. In AWS, look forGetCallerIdentityfrom instance roles near install time:aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=GetCallerIdentity \ --start-time 2026-10-21T11:30:00Z
If 1.107.0 ran on a host with outbound internet access, Upwind's advice is to assume the upload succeeded.
What to rotate
Rotate from a machine that never had the bad version. Rotating from an affected host hands new secrets to the same collector, and removing the package does not stop a loader that is already running.
| Credential | Why |
|---|---|
| Every provider key stored in the LiteLLM proxy database, plus the master key and virtual keys | The collector dumps the credentials table when DATABASE_URL is set |
The DATABASE_URL password and any secret in any process environment on the host | Environment variables are collected |
| Instance, task and workload roles, and static cloud keys | Metadata credentials are collected and validated |
Kubernetes service account tokens and files in /run/secrets | Read from the filesystem |
| SSH, GPG and TLS private keys | Read from home directories and system paths |
| GitHub CLI tokens, git credentials, PyPI and npm tokens | Publishing tokens enable further supply chain abuse |
| AI coding tool credentials and browser saved passwords on laptops | Targeted by name |
Afterwards, review provider dashboards and cloud, repository and registry audit logs for use of exposed credentials.
How to rebuild cleanly
Do not run pip install litellm==1.106.2 inside an affected environment. That starts Python and runs the payload again.
-
Preserve evidence if your process needs it, such as a disk snapshot or image export.
-
Contain. From the shell, kill the process holding
/tmp/remote_code_loader.lock, then deletecook.pth,cook.py,sitecustomize.pyand their.pycfiles from every affectedsite-packages, as Upwind describes. This is containment, not cleanup. -
Discard the environment. Delete the virtual environment or container, and reimage long-lived hosts that ran the loader, since it could have run arbitrary code.
-
Pin the clean version with hashes:
# requirements.in contains: litellm==1.106.2 pip-compile --generate-hashes -o requirements.txt requirements.in pip install --require-hashes --only-binary :all: -r requirements.txt # or with uv, which records hashes in uv.lock uv add 'litellm==1.106.2' && uv sync --locked -
Pin images by digest. Confirm the digest yourself on the GitHub issue first. The 17:03 probe there recorded the clean
v1.106.2index as:FROM ghcr.io/berriai/litellm:v1.106.2@sha256:7294b90f868c5c38c99b83021c7ba46f9b439e95f8e32964c925c7d2563a03f7Rebuild with
docker build --pull --no-cacheso no cached layer from the window survives. -
Purge caches and mirrors: pip and uv caches on runners, Docker layer caches, and any registry mirror that pulled LiteLLM in the window. A deleted tag does not prove a manifest is unreachable by digest.
-
Expect broken pulls. The floating tags were deleted, not repointed, so
FROM litellm/litellm:latestnow fails. Fix that with a digest pin.
For a general runbook, see our incident response playbook for a compromised dependency.
The provenance lesson
In the tensorlake npm compromise, the real repository and real workflow built a malicious commit, so the provenance was correct and unhelpful. LiteLLM 1.107.0 is the more useful case: the provenance told the truth, and the truth was suspicious.
The SLSA provenance BuildKit attached to each 1.107.0 image named the builder as BerriAI's own project-releaser workflow and the source as https://github.com/Berri-AI/litellm. Upwind checked the same releaser's records for v1.106.2, v1.106.1 and v1.107.0-rc.2, and each named BerriAI/litellm. Socket describes the lookalike as differing "by one hyphen from the legitimate" name. The evidence was in the image. Nothing downstream compared it with what it should have been.
A trusted builder is not a trusted source. If a release workflow accepts the source repository as an input, it needs an allowlist checked inside the workflow before anything is built or signed.
Identity checks must be exact. A person reading "Berri-AI/litellm" in a log will often see "BerriAI/litellm". Compare strings exactly, or better, compare the numeric owner and repository IDs in GitHub's OIDC tokens, which a lookalike account cannot copy. GitHub's gh attestation verify enforces exact matches through --repo, --signer-repo, --signer-workflow and --source-ref. BuildKit provenance can be read like this:
docker buildx imagetools inspect ghcr.io/berriai/litellm:v1.106.2 \
--format '{{ json .Provenance.SLSA }}' \
| grep -oE 'https://github.com/[^"]+' | sort -u
A deploy gate that failed unless every GitHub URL in that output began with the expected owner would have rejected the 1.107.0 image, as far as the public records show.
"Verified" commits meant little. Upwind notes that nine of the ten attacker commits carried GitHub's Verified badge because GitHub signs commits made in its web editor. The badge says where a commit was made, not who should have made it.
PyPI users had nothing to verify. SafeDep found no PyPI attestations for 1.107.0, 1.106.2 or rc2. Container users had provenance but no check; Python users had none.
Two approvals were not two people. BerriAI says one compromised account triggered the release and another approved it. Required reviewers help, but if the tokens behind them sit on laptops for months, one attacker holding several can satisfy the rule.
What would have helped
None of these is a guarantee. Each one shrinks the window, limits what one bad install reaches, or makes "are we affected?" a quick question.
For teams that consume Python packages and images
-
Pin exact versions and check hashes.
litellm==1.106.2with--hashentries, installed with--require-hashes, would not resolve 1.107.0, and hashes also catch a file swapped under an existing version. -
Add a release cooldown. pip 26.0 added
--uploaded-prior-to, and pip 26.1 accepts durations such asP3D. uv hasexclude-newer, which accepts values like"3 days", with per-package exemptions. The PyPI file was live for about 100 minutes, so even a one-day cooldown would have skipped it. Keep a documented exception path for urgent fixes.pip install --uploaded-prior-to P3D -r requirements.txt[tool.uv] exclude-newer = "3 days" -
Pin images by digest. Docker's documentation says a digest pin keeps the image "always the same", which also means updates arrive only when you change it. Never run
latestormain-stablein production. Let an update bot propose new digests after a minimum age. -
Restrict egress from gateway hosts. A gateway needs its providers, its database and your telemetry. Deny the rest. The collector posted to a fresh domain over plain HTTP.
-
Shrink what one host can reach. Where your deployment allows, keep provider keys in a secrets manager with scoped access and set spend limits on them.
-
Watch for new startup hooks. After installs in CI, list
.pthfiles containingimportlines and anysitecustomize.pyinsite-packages, and fail if the set changes unexpectedly. Both LiteLLM incidents this year used.pthfiles. -
Know who has which version, fast. A searchable inventory of every repository, image and SBOM by package and version turns the first incident question into a lookup. See SBOM-driven incident response and container image supply chain incidents in 2026.
For teams that publish
General practices, not an assessment of one project.
- Verify source identity inside the release workflow, by owner and repository ID, and reject free-form source inputs.
- Separate release credentials from jobs that install dependencies or run tests, as Socket's maintainer guidance says. Publish from a minimal final job that receives built artifacts.
- Publish attestations and use PyPI trusted publishing, as Upwind suggested in its report, so users have something to verify.
- Treat long-lived CLI tokens as release credentials if they can dispatch or approve a release. Shorten their lifetime and require hardware-backed second factors for approvals.
- Diff what you ship against what you tagged, including wheel data directories. That is how Upwind and SafeDep found the stray
data = "cook"line.
Zero-Day Discovery and Gold
Safeguard's Zero-Day Discovery looks for previously unknown vulnerabilities before a CVE exists, in an agentic mode and a deterministic scanner mode, across seven package ecosystems. Its findings are issued as SGZ advisories before they reach public CVE databases. That is a different problem from this incident: 1.107.0 was a poisoned release, not a flaw in LiteLLM's code.
Gold is about where components come from. Gold Open Source at gold.safeguard.sh is free with no login: searchable CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, plus a free read API and a CI gate GitHub Action. Gold Registry at registry.safeguard.sh is paid: hardened drop-in components and container images held to zero exploitable critical or high CVEs, SLSA L3+, Sigstore-signed, with evidence pages, and an artifact loses its badge on regression. We are not claiming Gold held a LiteLLM build or a record of this incident. The habit it supports is the relevant one: pull signed components from a curated source by digest, and be able to rebuild fast.
Where Safeguard fits
Safeguard does not detect malicious packages and would not have flagged 1.107.0 on its own. Its role is inventory and response once you know the bad version.
- Find every project with the version. Safeguard builds SBOMs and dependency inventories from repositories, container images and SBOM files, including transitive dependencies, and the platform shows which projects contain a given component and version.
- Scan locally or in CI.
safeguard scan --dir .scans a working tree,--image <name>scans a container image and--sbom <file>scans an SBOM, with JSON results under./outputthat you can search forlitellm1.107.0.safeguard execsuits CI. - Gate on findings. Once an advisory for 1.107.0 reaches the vulnerability databases Safeguard matches against, a policy gate in the platform can stop builds that still carry it. Until then, use the inventory search.
- Fix with approval. The CLI's assistant can pin
litellm==1.106.2with hashes, swap floatingFROMtags for digests and run tests, with your approval for each command./permissionsoffers Ask, Accept edits, Plan, Auto (the default) and Strict. Autonomous remediation can open fix PRs but is off by default.
It will not kill the loader, read egress logs, rotate keys, reimage hosts or check BuildKit provenance for a lookalike owner.
To try the CLI, download Safeguard and read the CLI documentation.
Common mistakes
- Running pip to check or fix.
pip show,pip freezeandpip installall start Python. Read the filesystem first. - Searching for the March indicator.
litellm_init.pthbelongs to March. This one usescook.pth,cook.pyandsitecustomize.py. - Trusting the tag. "We run
main-stable" does not clear you. Check the digest you pulled. - Forgetting the proxy database. Teams rotate cloud keys and miss the provider keys the proxy stores, the ones the collector dumps.
- Forgetting mirrors. PyPI's quarantine does not reach your internal mirror, and tag deletion does not reach your pull-through cache.
FAQ
Is this the same as the March 2026 LiteLLM compromise?
No. March involved 1.82.7 and 1.82.8 and a litellm_init.pth file, and LiteLLM attributed it to the Trivy dependency in its CI. October involves 1.107.0, three different files, container images as well as PyPI, and a release built from a lookalike repository.
I never imported LiteLLM. Am I safe?
No. The .pth file and sitecustomize.py run whenever Python starts in that environment, including for tests and pip commands.
Can I fix it by changing the version in place?
Not safely, because pip install starts Python and runs the payload again. Contain from the shell, then rebuild the environment or image on 1.106.2.
We use the Docker image. Are we affected?
Yes if you pulled v1.107.0, or pulled a floating tag between about 11:48 UTC and the early afternoon removal. No if you pinned v1.106.2, an older version or a known digest. Check the digest each container runs.
How did the attacker get in?
BerriAI says actions were taken through three engineers' GitHub accounts, apparently with stolen GitHub CLI OAuth tokens, and that it has not yet found how those tokens were obtained. The v1.107.0 tag itself is clean, per Upwind and SafeDep.
Which version should I use?
BerriAI says to pin 1.106.2 or another explicit version published before the incident. Upwind, Wiz and SafeDep also name 1.106.2. Watch the GitHub issue before moving off that pin.
Fix it without waiting for upstream
In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would have helped
These demos show the response side, not this incident.
The first is a real CLI session that fixes a lodash CVE: the assistant reads the finding, asks before each command, edits package.json, runs install and tests with approval and pushes nothing. For LiteLLM you would ask for the same kind of change in a Python service, pinning litellm==1.106.2 with hashes and replacing a floating image tag with a digest, approving each command.
Click through it at your own pace, or open it full screen.
The second is a real desktop app session. The CLI session shows in the conversation list, you ask for work on your computer, pick the folder, and Approve or Deny each command and file edit, ending with an upgrade done and tests passing. Here, an engineer could review each lockfile and Dockerfile change across several LiteLLM services from one window.
Click through it at your own pace, or open it full screen.
The third sets up the local runner on your own machine, approves it with a device code and scans from the terminal. For this incident, the runner would scan your gateway repositories and images on your own hardware so you can search the results for litellm 1.107.0.
Click through it at your own pace, or open it full screen.
Quick check
Your container runs ghcr.io/berriai/litellm:main-stable. How do you tell whether it ran the bad build?
Check the digest, not the tag. Compare the image ID from docker inspect or the pod's imageID with the infected digests published by Socket and Upwind. If it matches, treat the container and what it could reach as exposed, rotate from a clean machine, and redeploy v1.106.2 pinned by digest.
The 1.107.0 image carried provenance from the real release workflow. Why was that not enough?
The provenance recorded a source owner of Berri-AI instead of BerriAI, and nothing downstream compared it with the expected owner. Provenance protects you only when a verification step checks the exact source identity, ideally by numeric ID, and fails on a mismatch.
Related reading: October 2026 security incidents and what would have helped.
Sources
- BerriAI/litellm issue #47826, with Upwind's report and BerriAI's status updates
- Socket: LiteLLM compromise
- PyPI: litellm release history
- SafeDep: Compromised LiteLLM 1.107.0 on PyPI
- Wiz: LiteLLM compromised, steals credentials
- Aikido: LiteLLM compromised, infostealer and RAT in PyPI 1.107.0
- LiteLLM: Security update, suspected supply chain incident (March 2026)
- LiteLLM blog: security posts
- GitHub Advisory Database: GHSA-92x9-889m-jgmw (March 2026 versions)
- Python documentation: site
- pip documentation: Secure installs
- pip 26.0 release announcement
- uv settings reference: exclude-newer
- Docker documentation: docker image pull
- Docker documentation: SLSA provenance attestations
- GitHub CLI manual: gh attestation verify
- GitHub Docs: Using artifact attestations
Last reviewed: 21 October 2026. This is a developing story; check the linked advisories for the latest status.