{
    "archive_path": "archive/1792620512.392248",
    "base_url": "safedep.io/litellm-pypi-compromise-credential-stealer-backdoor",
    "basename": "",
    "bookmarked_date": "2026-10-21 22:08",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/safedep.io/litellm-pypi-compromise-credential-stealer-backdoor",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=safedep.io",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": "safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/index.html"
    },
    "domain": "safedep.io",
    "extension": "",
    "hash": "6RB2EJQ4A22HEC3Q5G75",
    "history": {
        "archive_org": [],
        "dom": [],
        "favicon": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://www.google.com/s2/favicons?domain=safedep.io"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-21T22:09:23.210678+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:22.693783+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--head",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-21T22:09:23.589436+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:23.220717+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [],
        "media": [],
        "mercury": [
            {
                "cmd": [
                    "/app/node_modules/@postlight/parser/cli.js",
                    "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
                ],
                "cmd_version": "1.0.0",
                "end_ts": "2026-10-21T22:09:33.481406+00:00",
                "index_texts": null,
                "output": "mercury",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:29.101064+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/app/node_modules/readability-extractor/readability-extractor",
                    "/tmp/tmp8i0zu676",
                    "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2026-10-21T22:09:29.088621+00:00",
                "index_texts": [
                    "\u2022  Oct 21, 2026   \u2022 6 min read      The LiteLLM 1.107.0 release on PyPI carries a credential stealer and a\nremote code execution backdoor. All seven wheels (Linux, macOS, Windows) install them as three files in\nsite-packages. Both run when any Python process starts on the host, even one that never imports litellm. The\nupstream GitHub source does not contain them. PyPI has since removed the release.\nVersions 1.106.2, 1.107.0rc1, and 1.107.0rc2 are clean. The companion packages that 1.107.0 pins,\nlitellm-proxy-extras==0.4.110 and litellm-enterprise==0.1.78, are also clean.\nWhat the attack does\nInstalling litellm==1.107.0 places three files in site-packages. Python loads two of them each time it starts.\n\ncook.pth imports cook.py, a credential stealer. It collects SSH keys, cloud credentials, CI/CD secrets, AI tool\ntokens, browser passwords, and the LLM provider keys in a LiteLLM proxy database. It encrypts the data and sends it to\nwww.litellm[.]dev.\nsitecustomize.py starts a background daemon. Every hour, the daemon reads a server address from a GitHub file,\nruns the stealer again, and downloads and runs code from that server.\n\nHow the implant got into the release\nThe pyproject.toml in the 1.107.0 source distribution (sdist) has one extra line in the maturin build section:\n    pyproject.toml (litellm-1.107.0.tar.gz)      TOML 6 lines    \nThe maturin data = \"cook\" setting copies a cook/ folder into litellm-1.107.0.data/purelib/ in the wheel. pip\ninstalls that folder into site-packages.\nThe sdist leaves out the cook/ folder, so this line is the only visible change. No public commit in\nBerriAI/litellm contains data = \"cook\" or a cook/ folder.\nThe wheel RECORD lists the three added files:\n    litellm-1.107.0.dist-info/RECORD      TEXT 4 lines    \nThe release does not match its tag\nThe tag v1.107.0 points to commit\n66c8f5b (06:59 UTC). Its\npyproject.toml says version = \"1.108.0\". SafeDep compared every sdist file with the git trees by blob hash. The sdist\nmatches a later main commit,\na8113b0 (08:50 UTC), plus five\nedited files and the cook/ folder.\nFour of the edited files are Cargo.toml files that gain a readme = \"README.md\" line. The legitimate 1.106.2 and rc2\nreleases have the same edit. The fifth is pyproject.toml, compared here with a8113b0:\n    pyproject.toml (a8113b0 \u2192 litellm-1.107.0.tar.gz)      DIFF 11 lines    \nThe edit sets the version back to 1.107.0 and pins the companion packages to the rc2 values. As a result, a dependency diff against rc2 shows nothing unusual. The legitimate 1.106.2 sdist matches its tag commit\n49943cd except for the\nCargo.toml readme lines.\nHow the wheels were built\nThe malicious wheels match the output of the normal BerriAI release pipeline:\n\nThe WHEEL file names maturin 1.9.4, the same version as in the legitimate releases. The implant files have correct RECORD entries, which shows no edits after the build.\nZip timestamps show separate native builds: macOS at 11:20, Linux at 11:21, and Windows at 11:37 UTC.\nIn the Windows wheel, all 2,804 .py files have CRLF line endings, including the three implant files. The Linux\nwheel has none. A git checkout on Windows with line-ending conversion produces this pattern.\n\nNo public GitHub Actions run built or published the release. SafeDep checked all 539 runs between 09:30 and 12:30 UTC.\nAt 11:50 UTC, about 10 minutes after the PyPI upload, litellm-dispatcher[bot] published the GitHub release\nv1.107.0. The same delay occurred for 1.106.2. PyPI has no provenance attestations for 1.107.0, 1.106.2, or rc2.\nSafeDep assesses, with moderate to high confidence, that BerriAI\u2019s private release pipeline built these wheels from an\nattacker-modified git source. The attacker could have used a compromised maintainer or bot credential, a private branch or mirror, or the reference the pipeline builds from. A stolen PyPI token used from the attacker\u2019s own machines fits the evidence less\nwell. Only BerriAI\u2019s pipeline logs can confirm the cause.\nPayload 1: the credential stealer in cook.py\nAt startup, Python runs each line that starts with import in a .pth file in site-packages. cook.pth contains\nimport cook, so the stealer runs in every Python process on the host: the application, pip, IDE language servers, and\nCI jobs.\ncook.py wraps about 61 KB of Python in one layer of exec(zlib.decompress(base64.b64decode('...'))). The decoded\ncode starts with this configuration:\n  \nWhat it collects\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nModuleTargetssysinfo, netinfoHost name, user, operating system, IP addresses, network settingsbrowser_pwChromium saved passwords, decrypted with the profile master key (DPAPI on Windows). Firefox logins.json and key4.dbbrowser_histBrowser historyssh_keys~/.ssh/*shell_histbash and zsh history, PowerShell PSReadLine, cmd historycloud_creds~/.aws/credentials and config, Azure profile and tokens, gcloud credentials, ~/.kube/config, ~/.docker/config.json, gh hosts.yml, rclone.conf, ~/.netrcdev_secrets.pem, .key, .pfx, .p12, .pk8, .ppk files under $HOME, Composer auth.json, FileZilla saved serverscontainer_credsKubernetes service account tokencicd_secretsGitLab runner config.toml, Jenkins initialAdminPassword and master.key, .vault-token, Terraform credentials.tfrc.json, Packer settingsai_devtoolsClaude Code, Claude Desktop, Cursor, Windsurf, Copilot, VS Code mcp.json, Gemini CLI, Codex auth.json, Continue, Aider, Goose, opencode, Qwen, Crush, Kiro, Zed, Hugging Face tokenenv_varsIts own environment and the environment of other processes through /proc/<pid>/environdb_litellmReads DATABASE_URL and dumps the LiteLLM_CredentialsTable and LiteLLM_ProxyModelTable tables, which hold the LLM provider API keys of a LiteLLM proxycloud_metadataAWS instance metadata service (IMDSv2, then v1): instance identity and role credentialsgpg_keys, tls_privkeyGPG keys, TLS private keys\nHow it sends the data\n\nIt encrypts the JSON results with a random AES-256-CBC key, and encrypts that key with RSA-OAEP and the embedded\npublic key.\nIt packs the result into a tar.gz (hdr.json, data.bin, key.bin) and sends it in a POST request to\nwww.litellm[.]dev/healthcheck with the header X-Token: XWiA8gZNa7NcX2XRoAEb.\nAfter a successful upload, it writes /tmp/.pc_once_72129118c59f5636c0bb so it collects only once per host. It\nalso sets PRIVCOLLECT_ACTIVE=1 to avoid a second run in the same process tree.\n\nPayload 2: the backdoor in sitecustomize.py\nPython imports sitecustomize at startup, so this file also runs in every Python process.\n  \n\nIt takes an exclusive lock on /tmp/remote_code_loader.lock, so only one daemon runs per host.\nIt calls fork() and setsid(), sends standard input and output to /dev/null, and changes directory to / to\ndetach from the parent process.\nEvery hour, it:\n\nreads domain.txt from the attacker\u2019s GitHub repository and takes the first line that looks like a domain,\nruns its embedded copy of the stealer, which has no once-per-host marker,\ndownloads http://<domain>/codepy and runs it with exec().\n\n\n\nThe attacker can point the daemon at a new server by editing domain.txt. Each infected host runs whatever that server\nreturns as codepy.\nAt 13:40 UTC, the GitHub account letters9813 and its repository returned 404, about two hours after the release. The\nbackdoor now finds no server address and does nothing. Hosts that ran the daemon before the deletion could have\nreceived and run second-stage code. SafeDep could not recover the server address.\nTimeline\nAll times are UTC on 2026-10-21.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTimeEvent06:59Tag v1.107.0 commit 66c8f5b created08:50Commit a8113b0 on main, the base of the release source10:44Legitimate 1.106.2 uploaded to PyPI11:20 to 11:37Malicious 1.107.0 wheels built11:39 to 11:401.107.0 uploaded to PyPI (all 8 files)11:50litellm-dispatcher[bot] publishes GitHub release v1.107.013:40Observed: 1.107.0 gone from PyPI, letters9813 account deleted\nIndicators of compromise\nNetwork\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nIndicatorPurposewww.litellm[.]dev, hxxp://www.litellm[.]dev/healthcheckReceives stolen dataHTTP header X-Token: XWiA8gZNa7NcX2XRoAEbSent with each uploadraw.githubusercontent[.]com/letters9813/pythonclient-test/main/domain.txtBackdoor reads its server address here (account deleted)hxxp://<domain>/codepySecond-stage code downloadRequests to 169.254.169.254 from Python processesTheft of AWS instance credentials\nHost\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nIndicatorMeaningcook.pth, cook.py, sitecustomize.py in site-packagesImplant installed/tmp/.pc_once_72129118c59f5636c0bbStealer already uploaded data/tmp/remote_code_loader.lockBackdoor daemon has runEnvironment variable PRIVCOLLECT_ACTIVE=1Present in infected processesDetached Python daemon (parent PID 1, own session, working directory /)Backdoor loop\nFile hashes (SHA-256)\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFileSHA-256litellm-1.107.0.tar.gz2befe2a79fb4fbbd9a543b5b40b530c8cffa8771bab22b8fb22b2e4ae3b1f819litellm-1.107.0-cp310-abi3-manylinux_2_28_x86_64.whl462d0c9f67ca522d9e0e4acd245e90b36ce0d5353604a66bb7c43a1ec4a6fcf7litellm-1.107.0-cp310-abi3-manylinux_2_28_aarch64.whl668169d35b87bf51324080390b1b1c5ab12bd335cfe5f1a99e3689abd5e02ae6litellm-1.107.0-cp310-abi3-musllinux_1_2_x86_64.whl1bbce8caa94f01c0aaa0b7ddae51553a8a32e1ce53d96d44fe583272aff7ad00litellm-1.107.0-cp310-abi3-musllinux_1_2_aarch64.whlc2e32d0ce2548aa662c18bf5001911a79fe5b30cf8357132275add02fdcde054litellm-1.107.0-cp310-abi3-macosx_10_12_x86_64.whl7598727f209d717041db37f7dca271d3a7d328a279890e0e1d55375729aeb86dlitellm-1.107.0-cp310-abi3-macosx_11_0_arm64.whl0aaf7ba346d41ef2f40390436738b001180d5a1c03aa093f121078a9969d9a1alitellm-1.107.0-cp310-abi3-win_amd64.whl12d4ccc5c6f8d35cf4f2cf233b7f3e418e863e657d81ff5f5fc9e210005b9484\nThe planted files have different hashes on Windows because of the CRLF line endings:\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFileLinux and macOSWindowscook.pth7f191632e7320705d154b85204f1ae546b543acbd7e8861229b76f5042b92bdb84dc8c82c0a4a4a62552ef826b49fcc86d1a5c4a5d0f2c06665723f93d129d7dcook.py9dd0a3926f96a37eb86cf1ee2310d9be93139cc4ebef3ef9e2efe9531390be2503ac2a8b752605d1d7710979c17ff18b03dd978203ba23ba97fd8ce3d6f897easitecustomize.py34bef04fe88ac9d0aae0475630b1738681d15b94f233ac8aa46eacf5968305fab6122440d0d9cb1eb1ade9933f49214da8c31288de7710c076c2680dfa528465   pypipythonmalwaresupply-chainlitellm       Author       Share         The Latest from SafeDep blogs    Follow for the latest updates and insights on open source security & engineering                    Ship Code.   Not Malware.     Start free with open source tools on your machine. Scale to a unified platform for your organization."
                ],
                "output": "readability",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:26.539951+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--max-time",
                    "120",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) curl/curl 8.14.1 (x86_64-pc-linux-gnu)",
                    "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
                ],
                "cmd_version": "curl 8.14.1 (x86_64-pc-linux-gnu)",
                "end_ts": "2026-10-21T22:09:26.529195+00:00",
                "index_texts": null,
                "output": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor - Real-time Open Source Software Supply Chain Security",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:26.494218+00:00",
                "status": "succeeded"
            }
        ],
        "wget": [
            {
                "cmd": [
                    "wget",
                    "--no-verbose",
                    "--adjust-extension",
                    "--convert-links",
                    "--force-directories",
                    "--span-hosts",
                    "--no-parent",
                    "-e",
                    "robots=off",
                    "--reject=js,mjs,woff,woff2,ttf,otf,eot,mp4,m4v,mov,webm,ogv,mkv,avi,flv",
                    "--timeout=120",
                    "--restrict-file-names=windows",
                    "--page-requisites",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 ArchiveBox/0.7.4 (+https://github.com/ArchiveBox/ArchiveBox/) wget/GNU Wget 1.21.3",
                    "--compression=auto",
                    "--timestamping",
                    "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
                ],
                "cmd_version": "GNU Wget 1.21.3",
                "end_ts": "2026-10-21T22:09:26.484958+00:00",
                "index_texts": null,
                "output": "safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/index.html",
                "pwd": "/data/archive/1792620512.392248",
                "schema": "ArchiveResult",
                "start_ts": "2026-10-21T22:09:23.601917+00:00",
                "status": "succeeded"
            }
        ]
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": null,
        "dom": null,
        "favicon": "favicon.ico",
        "git": null,
        "media": null,
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor - Real-time Open Source Software Supply Chain Security",
        "warc": null,
        "wget": "safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/index.html"
    },
    "link_dir": "/data/archive/1792620512.392248",
    "newest_archive_date": "2026-10-21T22:09:29.101064+00:00",
    "num_failures": 0,
    "num_outputs": 6,
    "oldest_archive_date": "2026-10-21T22:09:22.693783+00:00",
    "path": "/litellm-pypi-compromise-credential-stealer-backdoor/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_id": "15dbf525-6813-4cc6-94dd-13cd50d36054",
    "sources": [
        "/data/sources/1792620512-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1792620512.392248",
    "title": "Compromised LiteLLM 1.107.0 on PyPI Installs a Credential Stealer and Backdoor - Real-time Open Source Software Supply Chain Security",
    "updated": "2026-10-21T22:09:22.692076+00:00",
    "updated_date": "2026-10-21 22:09",
    "url": "https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/"
}