Guide · AI and GenAI
LiteLLM's October 2026 PyPI Compromise Explained: Stolen Tokens, Startup Hooks
23 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- The LiteLLM PyPI compromise was a poisoned release, litellm 1.107.0, published on 21 October 2026 through LiteLLM's own release pipeline. PyPI is the public store that pip installs Python packages from.
- The bad wheels (ready-to-install package files) added a .pth file. Python reads .pth files from site-packages, the folder pip installs into, at every start, so a credential stealer ran in every Python process started in that environment, even pip.
- The attacker used stolen GitHub CLI tokens to disable branch protection and point the release workflow at a lookalike repository. A second stolen account passed the approval step.
- If you installed 1.107.0 or ran the affected Docker images, do not just run pip install again. Delete the planted files, stop the background process, rebuild, and rotate every reachable secret.
- Beginners: read up to the harmless demo, then jump to the checking and clean-up sections. Engineers get a tested .pth demo on Python 3.13, 3.14 and 3.15, one version quirk the Python docs do not mention, and why trusted publishing did not stop this.
What goes wrong when a trusted Python package ships a bad release?
Your machines run the attacker's code the next time any Python program starts in an environment where the bad release is installed. You do not need to import the package, and in this case you did not even need to run it. That is what made the LiteLLM PyPI compromise so wide: the payload rode on Python's own startup.
Picture a common setup. A small team runs the LiteLLM proxy, an open-source gateway that sits in front of several model providers, on one cloud VM in Mumbai. The setup is made up, but the mechanism and the dates are real.
The proxy's database holds every provider API key the team pays for. A nightly job rebuilds the proxy image with an unpinned pip install litellm, so it always takes the newest release.
On 21 October 2026, that job ran at 12:10 UTC (made up). The newest release on PyPI at that moment was the poisoned 1.107.0. Nothing failed, every test passed, and the proxy served traffic as usual.
Meanwhile, every Python process in the proxy container ran a credential stealer at startup. We will follow this VM through the whole guide: how the code got in, how to check, and how to clean up.
If you only need to know whether you were hit, jump to the section on how to check, then follow the clean-up steps after it.
Which words do you need before reading about the LiteLLM PyPI compromise?
- PyPI: the Python Package Index, the public registry that pip downloads packages from.
- Wheel: a ready-built package file ending in .whl. pip unzips it into your environment without running a build step.
- site-packages: the folder inside a Python install or virtual environment (venv) where pip puts packages.
- .pth file: a small text file in site-packages. Most lines add folders to Python's import path, but a line starting with import is run as code.
- sitecustomize.py: a module Python tries to import automatically at startup, meant for local settings.
- Release pipeline: the automated CI workflow that builds, signs and uploads a project's packages and images.
- Image digest: a sha256 fingerprint of a container image. A tag like latest can move; a digest never changes.
What was the LiteLLM PyPI compromise on 21 October 2026?
The LiteLLM PyPI compromise was a malicious build of litellm 1.107.0 that LiteLLM's real release pipeline signed and published. The maintainers' incident report says it steals credentials and loads remote-code-execution tools. The report calls it compiled malware; SafeDep's file-level analysis describes obfuscated Python wrapped in compressed, base64-encoded text.
The source code on GitHub was not the problem. The report says the official v1.107.0 tag does not contain the malicious files. The artifacts were built from an attacker-controlled lookalike repository named Berri-AI/litellm, one dash away from the real BerriAI/litellm.
| Artifact | Names or tags | Live window | Status as of 23 Oct 2026 |
|---|---|---|---|
| PyPI package | litellm 1.107.0, all seven wheels (SafeDep and the GitHub issue add that the source archive had one tampered build line) | 11:39:51 to 13:23 | Removed from PyPI |
| Docker Hub image | v1.107.0, latest, main-latest, main-stable | 11:46:56 to 14:20 | Removed or repointed to clean images |
| GHCR images | litellm, litellm-database, litellm-non_root with the same tags | 11:46:56 until made unavailable by 13:51 | Made unavailable |
| Git source | Official v1.107.0 tag | Not affected | Does not contain the malicious files |
In our running example, the 12:10 UTC build sits inside the PyPI window. A team that pulled the latest Docker tag at 14:00 UTC would also be inside the Docker Hub window.
The clean version the report tells you to pin is litellm 1.106.2, released on PyPI earlier the same day.
How did a .pth file run code without anyone importing LiteLLM?
Python's site module, the part of the standard library that sets up site-packages at every start, runs every .pth line that starts with import, at every interpreter start. The Python docs say these lines are executed with exec(), Python's built-in for running a string as code, for backward compatibility. Nothing checks which package dropped the file there.
SafeDep's analysis says the poisoned wheels added three files to site-packages: cook.pth, cook.py and sitecustomize.py. The cook.pth file holds a single line, import cook, which loads the stealer in cook.py.
SafeDep says sitecustomize.py starts the backdoor, a detached background process. So the stealer and the backdoor both start whenever any Python process using that install or venv starts: the proxy, a cron script, pip or an editor helper.
Here is the analogy for the hardest idea. Installing a package is like letting a contractor into your building once to fit a cupboard. A .pth import line is that contractor secretly keeping a copy of the front-door key. Every morning when the building opens, they walk in first.
Where the picture breaks: a real contractor shows their face. A .pth line runs silently, prints nothing unless it chooses to, and needs no permission beyond being able to write into site-packages once.
Pause and predict: you run python -c 'pass', a program that does nothing. Which code from the poisoned wheel runs before it exits? Hold your guess and check it against the diagram.
What happens at every Python start once the files are planted
Drag to exploreNotice that the stealer runs before your own code and without any import of litellm. The -S flag is the only switch in this path that skips the site step.
Diagram connections:
- Any python process starts to site module runs (import site)
- site module runs to Reads each .pth file (scan site-packages)
- Reads each .pth file to cook.py stealer (import cook)
- site module runs to sitecustomize.py (import sitecustomize)
- sitecustomize.py to Your program runs (then your code)
- cook.py stealer to Attacker servers (exfiltrate)
- sitecustomize.py to Attacker servers (poll for code)
- Any python process starts: Proxy, pip, cron job, IDE helper
- site module runs: Skipped only with python -S
- Reads each .pth file: Lines starting with import are exec'd
- cook.py stealer: Collects keys, tokens, env vars (SafeDep)
- sitecustomize.py: Starts detached backdoor daemon (SafeDep)
- Your program runs: Looks completely normal
- Attacker servers: Encrypted upload, hourly code fetch
Try the mechanism with a harmless demo
You can watch this happen safely with a .pth file that only prints a line. We ran this on Python 3.15.1, 3.14.6 and Debian's 3.13.17 on 23 October 2026. Use a throwaway venv and delete it afterwards.
# any python3 works; on 3.13 and 3.14 the line prints twice in a venv
python3.15 -m venv /tmp/pth-demo
SP=$(/tmp/pth-demo/bin/python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
printf 'import sys; print("[demo.pth] ran", file=sys.stderr)\n' > "$SP/demo.pth"
/tmp/pth-demo/bin/python -c 'pass' # prints: [demo.pth] ran
/tmp/pth-demo/bin/pip --version # prints it too: pip is Python
/tmp/pth-demo/bin/python -I -c 'pass' # isolated mode: still prints
/tmp/pth-demo/bin/python -S -c 'pass' # no site module: prints nothing
rm -rf /tmp/pth-demoTo check it worked, look for the line [demo.pth] ran on stderr before each command's own output. Only the -S run stays quiet. Isolated mode, -I, ignores environment variables and your user site folder, but it still processes the venv's site-packages.
What just happened? You never imported anything, yet your line printed on every start, even for pip. Only -S, which skips the site step, kept it quiet. That is how cook.pth reached every Python process in the Mumbai proxy container.
Beginners, you have the core idea now. Jump ahead to the section on how to check whether you were affected. The next parts are for engineers: version quirks, how the pipeline was taken over, and why trusted publishing did not stop it.
Details that change between Python versions
Our runs found three differences worth knowing. The first one is undocumented as of 23 October 2026, so pin your version if you rely on it.
| Behaviour | Debian Python 3.13.17 | Python 3.14.6 (standalone build) | Python 3.15.1 (standalone build) |
|---|---|---|---|
| .pth import line runs at a plain start | Yes, twice per start | Yes, twice per start | Yes, once per start |
| sitecustomize.py placed in the venv runs | No, Debian's own copy loads first | Yes | Yes |
| Runs under python -I | Yes | Yes | Yes |
| Runs under python -S | No | No | No |
| Hidden .pth file (name starts with a dot) | Skipped | Skipped | Skipped |
| Any warning shown by default | No | No | No, a deprecation note only with -v |
The double run on 3.13.17 and 3.14.6 comes from site.py itself. Inside a venv, the venv setup step and the main step both scan the same site-packages, so import lines execute twice. Python 3.15.1 tracks processed folders and runs them once.
The Debian result is not a defence. Python imports the first sitecustomize it finds on sys.path, and Debian ships one earlier on that path. The cook.pth stealer still ran, and many Python builds, like the two standalone ones we tested, have no sitecustomize of their own.
Python 3.15, released on 9 October 2026, deprecates .pth import lines under PEP 829 and adds .start files for startup entry points. PEP 829 expects import lines to be silently ignored from Python 3.18. In our test, a .start entry point also ran at every start. The risk moves to a new file type; it does not go away.
How was LiteLLM's release pipeline abused?
The attacker did not upload to PyPI by hand. They made LiteLLM's real, signed release workflow build from their own lookalike repository, using stolen developer tokens at every gate.
The report says LiteLLM found an unexpected, innocuously named user account on an internal LiteLLM deployment that was exposed to the public internet. It believes the attacker used that access to read three developers' GitHub CLI OAuth tokens from the deployment's logs.
Why were tokens in logs? The report says a coding assistant can, in specific situations, embed a user's GitHub CLI token in a response body, and those bodies may appear in LiteLLM logs. How the attacker reached that deployment is still unconfirmed as of 23 October 2026.
One more factor made the timing worse. The report says release permissions had been expanded temporarily that Monday, 19 October, so that two engineers could publish bugfix backports for customers.
10:08:29 UTC: branch protection says no
An unauthorised change to the private release workflow is rejected by branch protection, the GitHub rule that blocks direct pushes.
10:08:34 UTC: an owner token overrides it
Five seconds later, a token tied to a GitHub organisation owner disables the protection and force-pushes the change.
10:46:39 UTC: staging builds the fake source
The modified workflow runs a staging build from Berri-AI/litellm, the lookalike repository. It completes successfully at 11:08:09.
11:11:18 UTC: a second account approves
A credential from a second developer satisfies the production approval gate. The gate checked who approved, not whether that person was really at the keyboard.
11:39:51 UTC: the wheels go out
Seven malicious litellm 1.107.0 wheels upload to PyPI. Container images follow at 11:46:56 UTC.
11:53:49 UTC: tracks are covered
A third developer credential deletes production and staging workflow run records, which limits the logs investigators can read.
13:23 to 14:20 UTC: clean-up
PyPI removes 1.107.0 at 13:23. By 14:20, every affected Docker Hub tag is removed or repointed to verified clean images.
How stolen tokens turned a real pipeline into the attacker's
Drag to exploreEvery gate in this chain worked as designed. Each one trusted a token, and every token was stolen. Notice that the signing and upload steps were LiteLLM's own.
Diagram connections:
- Exposed internal proxy to Proxy logs (attacker reads)
- Proxy logs to Org owner token (tokens reused)
- Org owner token to Release workflow (force-push change)
- Release workflow to Production approval (waits for approval)
- Production approval to PyPI and registries (build, sign, upload)
- PyPI and registries to pip and docker users (11:39 to 14:20 UTC)
- Exposed internal proxy: Entry route unconfirmed as of 23 Oct 2026
- Proxy logs: Held three developers' CLI tokens
- Org owner token: Disabled branch protection
- Release workflow: Now checks out Berri-AI/litellm
- Production approval: Passed with a second stolen account
- PyPI and registries: Signed 1.107.0 wheels and images
- pip and docker users: Unpinned installs pull 1.107.0
Why did trusted publishing and approvals not stop the attack?
Because both controls check who is publishing, not what is being published. The attacker ran LiteLLM's own trusted workflow, so every identity check passed.
Trusted publishing lets a CI job swap a short-lived OpenID Connect identity token for a PyPI upload token. PyPI's docs say that upload token is valid for only 15 minutes, so there is no long-lived password to steal.
The report says that after a prior incident in March, LiteLLM moved releases to a private repo and created them through trusted GitHub publishers. It also made Docker tags immutable and signs its images.
Those steps still helped: the attacker had to take over the workflow itself rather than copy a token. But the workflow was told to check out a different repository, and nothing compared that source with the official tag.
The approval gate had the same gap. A GitHub environment with required reviewers waits for one listed person to approve. If that person's token is stolen, the approval is real as far as GitHub can tell.
This is the lesson that sets this incident apart from the two npm incidents covered elsewhere on SevyDevy. In the axios case, malware ran from an install hook; in the TanStack case, a publish token was taken from CI (see the related guides). Here, the payload ran at Python startup and the release workflow was rewritten through stolen developer tokens.
How do you check whether the LiteLLM PyPI compromise affected you?
Check two things: whether 1.107.0 or the bad image ever reached a machine during the window, and whether the planted files are still there. The first matters more, because a rebuild may have erased the second.
In our running example, the nightly build on 22 October would have installed 1.106.2. The planted files would be gone, yet the keys were stolen on 21 October. So start with your build and pull history, not only with what is on disk today.
Be careful with pip here, because pip is a Python program. In our test, pip show ran the planted .pth line before it printed anything. On a machine you believe is clean, pip show litellm is fine. On a suspect machine, use find and ls first.
# 1. Did it ever arrive here? Search CI, build and deploy logs (no Python needed)
grep -rE 'litellm-1[.]107[.]0([^.0-9a-z]|[.]tar|$)|litellm==1[.]107[.]0([^.0-9a-z]|$)|litellm [(]1[.]107[.]0[)]' \
/path/to/your/build-logs 2>/dev/null # skips the clean rc1 and rc2 builds
grep -ri 'litellm[.]dev' /path/to/dns-or-proxy-logs 2>/dev/null # exfiltration domain
# 2. Planted files, the bad version folder, and the marker and lock files,
# without starting Python (this also searches container layers under /var/lib/docker)
sudo find / \( -name cook.pth -o -name cook.py -o -path '*/__pycache__/cook.*.pyc' \
-o -path '*-packages/sitecustomize.py' -o -name 'litellm-1.107.0.dist-info' \
-o -name '.pc_once_72129118c59f5636c0bb' -o -name remote_code_loader.lock \) \
-not -path '/proc/*' 2>/dev/null
# 3. Marker written after a successful upload (SafeDep), and the backdoor's lock file
ls -l /tmp/.pc_once_72129118c59f5636c0bb /tmp/remote_code_loader.lock
sudo lsof /tmp/remote_code_loader.lock # which process holds the lock
# 4. Images still on this machine (removed images will not show)
docker image ls --digests | grep -i litellm
# 5. On a machine you trust, the normal version check
pip show litellm | head -2Expected results on a clean machine: steps 1 and 2 print nothing, and step 3 says no such file for both paths. Step 4 shows none of the digests listed in the next paragraph, and step 5 shows a version other than 1.107.0, or says the package is not found.
The report gives the full compromised Docker Hub index digest as sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e. For GHCR, the report lists three indexes, shortened as it prints them: litellm sha256:1bbcb1443bc9, litellm-database sha256:c00f9d92c427 and litellm-non_root sha256:7b00e91cd2fe. An index digest is the fingerprint of the multi-platform image list that a tag points to. Also check your registry, cluster or runtime pull records for these digests between 11:46 and 14:20 UTC.
Step 2 looks for sitecustomize.py only inside site-packages or dist-packages folders. Distributions keep their own copies under /etc/python3.x and /usr/lib/python3.x, and pip leaves some in /tmp/pip-build-env folders; those are normal. The strong signals are cook.pth, cook.py and the litellm-1.107.0.dist-info folder.
SafeDep and the public GitHub issue name the exfiltration endpoint as www.litellm[.]dev/healthcheck, written here with brackets so it cannot be clicked. The project's real domain is litellm.ai. Any lookup of the lookalike domain from your network is a strong sign of theft.
SafeDep lists two more signs. A process environment containing PRIVCOLLECT_ACTIVE=1 is a strong one; sudo grep -l 'PRIVCOLLECT_ACTIVE=1' /proc/[0-9]*/environ lists such processes without starting Python.
Python processes contacting 169.254.169.254, the cloud instance metadata address the stealer queries for role credentials, are only a lead. AWS SDKs and other normal tools on a cloud VM contact that address too, so look for processes that have no reason to.
Do not forget the less obvious places. Check CI runners, build caches, developer laptops, notebook servers and every venv, not only the proxy host. Each Python install and venv reads its own site-packages, so each one needs its own check.
The report says that if the exfiltration marker file exists, the theft is confirmed. SafeDep gives its path as /tmp/.pc_once_72129118c59f5636c0bb. If it does not exist, that proves nothing, because containers and temp-folder clean-up remove it.
If any build or pull fell between 11:39 and 14:20 UTC on 21 October, treat that machine as affected even if every check above comes back clean today.
On macOS, the same find and lsof commands work. On Windows, search each Python's Lib\site-packages folder for cook.pth and cook.py. The public GitHub issue says the backdoor cannot start there, but the stealer still runs, so rotate anyway.
What should you do if you installed LiteLLM 1.107.0?
Assume your secrets are gone, remove the payload without starting Python in that environment, rebuild from clean inputs, and rotate everything the machine could reach. These steps follow the official report, as of its 21 October 2026 20:30 UTC update, plus one wider rotation step from SafeDep.
Do not start with pip install
Any Python start in the affected environment runs cook.pth and sitecustomize.py again, and pip is Python. Clean up first.
Delete the planted files
Remove cook.pth, cook.py and sitecustomize.py from every affected site-packages folder, plus their matching __pycache__ entries.
Stop the background process
Kill the detached Python daemon holding /tmp/remote_code_loader.lock. SafeDep describes it as a Python process with parent PID 1, its own session and working directory /. SafeDep says it re-runs the stealer every hour, so stop it before you rotate anything.
Rebuild rather than repair
Reinstall pinned to litellm==1.106.2, or better, rebuild images and venvs from scratch, which the report recommends.
Assume exfiltration if online
If 1.107.0 ran on a host with outbound internet access, treat the stolen data as already sent.
Rotate every reachable secret
Rotate the instance or task cloud role, every provider key in the proxy database, master and virtual keys, the DATABASE_URL password and secrets in any process's environment.
Widen the list on laptops and CI
SafeDep says the stealer also takes SSH keys, cloud and Kubernetes credentials, GitHub CLI and CI secrets, Docker registry logins, private key files, AI tool tokens and browser-saved passwords. Rotate those, and check shell history for pasted secrets.
Look for misuse
Check your cloud audit trail, CloudTrail on AWS, for GetCallerIdentity calls from the instance role around install time, and for that role's credentials used from outside your network.
For our Mumbai VM, that means a fresh VM image, a pinned 1.106.2 install and new keys at every model provider the proxy used. The old VM's disk is kept only for investigation, not reused.
SafeDep adds one more reason to rebuild. The backdoor fetched code from a server address stored in an attacker GitHub repository every hour. That account returned 404 at 13:40 UTC.
The evidence on that second stage is mixed. The GitHub issue reporter found the address file empty since 20 October, so the stage looked dormant. SafeDep could not recover the address, so it cannot rule out that some hosts ran unknown code.
How do you harden Python installs and release pipelines after this?
Make new releases opt-in for your installs, and make your own releases need more than one stolen token. Each control below covers a different link in the chain, and none covers all of them.
If you install Python packages
Pin exact versions and add hashes, so a new release cannot slip in through a routine rebuild. In our test with pip 26.1.2, a wrong hash stopped the install with THESE PACKAGES DO NOT MATCH THE HASHES. An unpinned line under --require-hashes was refused too.
# Every dependency needs its own pinned, hashed line, so generate the file
# with a lock tool such as pip-tools or uv rather than by hand.
# One line looks like this (pip hash path/to/file.whl prints the hash):
# litellm==1.106.2 --hash=sha256:...
pip install --require-hashes -r requirements.txt
# Containers: pin the image by digest, not by a tag that can move
# FROM ghcr.io/berriai/litellm@sha256:DIGEST_YOU_VERIFIEDBe precise about what this buys you. A lock made before 21 October would have refused 1.107.0. A lock refreshed during the window would have recorded the malicious file's hash and trusted it. Hashes freeze a choice; they do not judge it.
If you publish Python packages
| Control | What it does | Would it have stopped this attack? |
|---|---|---|
| Trusted publishing on PyPI | CI gets a 15-minute upload token instead of a stored password | No on its own: LiteLLM already used it, and the workflow itself was hijacked |
| Block admin bypass on branch rules | Branch rules apply to admins and owners too | Partly: whoever can edit the rule can still turn it off |
| Required reviewers plus prevent self-review | A named person must approve, and not the one who started the run | No: a stolen token approves as its owner |
| Fewer org owners and short-lived tokens | Fewer tokens can change everything, and stolen ones expire | It narrows this route: an owner token disabled the protection |
| Build only from the tagged source | The workflow refuses any repository or ref except the official tag | It blocks this exact step, unless the attacker can also edit the check |
| Never log auth headers or token-bearing bodies | Logs stop being a token store | It closes this route: the tokens came from logs |
GitHub's docs say administrators can bypass environment protection rules by default, and that you can block this. Branch protection likewise skips admins unless you tick Do not allow bypassing the above settings.
Also treat any proxy that sees request and response bodies as a secret store. An LLM gateway can log prompts and replies for debugging, so it can capture whatever a tool pastes into a reply. Scrub tokens before they are written.
How do you explain the LiteLLM PyPI compromise in a system design interview?
Frame it as a trust chain with four links: source, build, publish and install. Name which link failed and which control guards each one. A strong answer separates identity from integrity.
State the failure in one sentence
A release workflow was rewritten with stolen tokens to build from a lookalike repository, and the signed package ran a stealer at every Python start.
Walk the chain
Source is the git tag, build is CI, publish is the registry upload, and install is pip or docker pull on user machines.
Place each control
Branch rules and reviewers guard source and build. Trusted publishing guards publish. Pins, hashes and digests guard install.
Name the gap
Every control checked an identity, and every identity was stolen. Nothing checked that the built source matched the official tag.
Propose a fix with a trade-off
Build only from the tagged source and cut owner tokens. The cost is slower urgent releases; the report notes permissions had been widened on Monday 19 October for backports.
Cover detection and recovery
Inventory installs by version and image digest, rebuild instead of repair, and rotate every secret the machine could reach.
Questions people ask
- Which LiteLLM versions are safe to install?
- As of 23 October 2026, the clean pin is litellm==1.106.2, or any explicit version published before the incident, per the report. SafeDep also lists 1.106.2 and the 1.107.0 release candidates as clean. A separate vulnerability is due to be disclosed on 27 October, so recheck the report then.
- I never imported litellm in my code. Could I still be affected?
- Yes. The stealer was started by cook.pth, which Python reads at every interpreter start, so importing litellm was never needed. If 1.107.0 was installed in an environment, any Python program started there ran the payload, including pip, test runners and scheduled scripts.
- Was the LiteLLM source code on GitHub backdoored?
- No, according to the maintainers and the people who analysed it. The report says the official v1.107.0 tag does not contain the malicious files. The artifacts were built from a lookalike repository named Berri-AI/litellm. Building from the official source would not have produced the bad files.
- How did the attacker get into LiteLLM in the first place?
- That part is not confirmed as of 23 October 2026. The report believes the attacker reached an internet-exposed internal LiteLLM deployment and read developers' GitHub CLI tokens from its logs. How they got into that deployment is unknown; the maintainers suspect a recently patched vulnerability.
- Does running Python with -I protect me from .pth files?
- No. In our tests on Python 3.13, 3.14 and 3.15, isolated mode still ran .pth import lines. It also ran the venv's sitecustomize on 3.14 and 3.15; on Debian's 3.13, Debian's own copy loaded instead. Only -S, which skips the site module entirely, stopped them. With -S, site-packages is left off the import path, so in our test a pip-installed package could not even be imported.
- Will Python 3.15 stop this kind of attack?
- Not by itself. Python 3.15 deprecates .pth import lines but still runs them, with a notice only under -v; PEP 829 expects them to be ignored from 3.18. It also adds .start files, which run entry points at startup. A malicious package can use either.
- Do I need to rotate keys if I only pulled the Docker image?
- Yes, if a container actually ran the affected image digest. The report says to treat such a container as compromised. Rotate the cloud role, provider keys in the proxy database, master and virtual keys, database passwords and any secret in the container's environment.
Sources and further reading
- LiteLLM: Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised (official incident report, update of 21 Oct 2026 20:30 UTC)
- SafeDep: Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor
- GitHub issue 47826: litellm 1.107.0 wheels on PyPI are compromised
- PyPI: litellm 1.106.2 release files
- Python docs: site, site-specific configuration hook (.pth, .start and sitecustomize)
- PEP 829: startup entry points and the deprecation of .pth import lines
- AWS docs: Boto3 credentials (instance metadata service as a credential source)
- PyPI docs: Trusted Publishers
- pip docs: Secure installs and hash-checking mode
- GitHub docs: Deployments and environments (required reviewers, self-review, admin bypass)
- GitHub docs: About protected branches
Related guides
- TanStack npm Hack Explained: What JavaScript Developers Must Do NowBackend · 18 min read
- npm October 2026 SlugFest Flaw Explained: How Reclaimed Repo Names Could PublishBackend · 20 min read
- Axios Supply Chain Attack 2026: Malicious npm Versions, Postinstall Malware, and How to Protect Your SystemsBackend · 16 min read