21 октября 2026 года команда LiteLLM сообщила в своём блоге, что её релизный конвейер взломали, и на PyPI и в Docker-реестры ушла версия 1.107.0 с вредоносом. Он крадёт учётные данные и умеет подтягивать с сервера злоумышленника дополнительный код. LiteLLM это Python SDK и шлюз, через который приложения ходят к моделям разных провайдеров по одному API. По данным BleepingComputer, у пакета около 92 млн загрузок в месяц.
Если у вас на сервере или в CI стоит LiteLLM без жёстко закреплённой версии, или контейнер берётся по тегу latest, стоит проверить, что именно подтянулось 21 октября. Опасна эта версия не только для тех, кто реально пользуется шлюзом: вредонос срабатывал при любом запуске Python в заражённом окружении.
Что было заражено и когда
По словам LiteLLM, затронуты такие артефакты (время московское):
- PyPI: все семь wheel-пакетов
litellm==1.107.0, лежали с 14:39 до 16:23; - Docker Hub: теги
v1.107.0,latest,main-latestиmain-stableуказывали на заражённый образ с 14:46 до 17:20; - GHCR: те же теги у образов
litellm,litellm-databaseиlitellm-non_rootс 14:46 до 16:51.
Исходники по тегу v1.107.0 на GitHub чистые: заражённые пакеты собрали из подставного репозитория Berri-AI/litellm, похожего на настоящий BerriAI/litellm. Предыдущие версии, по словам команды, безопасны, а клиенты закрытой корпоративной сборки не пострадали. Заражённый релиз с PyPI уже убран, а 24 октября там вышла версия 1.106.3.
Почему хватало просто запустить Python
Сам код LiteLLM злоумышленник не трогал. В пакет добавили три файла: cook.pth, cook.py и sitecustomize.py. При установке они ложатся прямо в каталог site-packages, а не внутрь папки litellm. Python при каждом старте обрабатывает файлы .pth и выполняет в них строки с import, а потом пытается импортировать модуль sitecustomize. В cook.pth была одна строка:
import cook
Как разобрали в Microsoft, cook.py распаковывает спрятанный в base64 и zlib сборщик и запускает его в фоновом потоке. Импортировать litellm не нужно: в одном из случаев, которые видела Microsoft, сборщик сработал на Windows, когда человек ставил через pip совсем другую зависимость. Отсюда главное правило: в подозрительном окружении не запускать ни python, ни pip.
Сборщик искал всё, до чего мог дотянуться:
- ключи и конфиги AWS, Azure, Google Cloud, Kubernetes и Docker, а также временные ключи из метаданных облака;
- токены Git, GitHub, npm, PyPI и CI/CD, файлы .env;
- SSH-, GPG- и TLS-ключи;
- настройки и токены ИИ-инструментов, IDE и MCP;
- сохранённые пароли, куки и историю браузеров, историю shell;
- переменные окружения, на Linux ещё и чужих процессов через /proc;
- ключи провайдеров и настройки моделей из базы прокси LiteLLM.
Собранное шифровалось (AES-256 плюс RSA-4096) и отправлялось POST-запросом на www.litellm.dev/healthcheck, домен под видом настоящего проекта. После успешной отправки появлялся файл-метка /tmp/.pc_once_72129118c59f5636c0bb.
Второй файл, sitecustomize.py, работал на Linux и macOS. Он запускал фоновый процесс с блокировкой /tmp/remote_code_loader.lock, который каждый час читал адрес сервера из файла на GitHub и мог скачать и выполнить оттуда новый код на Python. По данным Microsoft, файл с адресом был пустым, и второй этап никто не наблюдал, но злоумышленник мог включить его в любой момент.
Как проверить сервер
Проверять снаружи, обычными утилитами shell, не запуская интерпретатор из подозрительного окружения. Поиск следов в файловой системе и временном каталоге:
find / -xdev \( -name 'cook.pth' -o -name 'cook.py' -o -name 'litellm-1.107.0.dist-info' \) 2>/dev/null ls -la /tmp/remote_code_loader.lock /tmp/.pc_once_72129118c59f5636c0bb 2>/dev/null ps aux | grep -i '[r]emote_code_loader\|[c]ook'
Версию в lock-файлах проектов и кэшах pip и uv тоже стоит поискать:
grep -rnE 'litellm(==|-)1\.107\.0' --include='*.txt' --include='*.lock' --include='*.toml' ~ /srv /opt 2>/dev/null grep -rn -A1 'name = "litellm"' --include='*.lock' ~ /srv /opt 2>/dev/null | grep '1.107.0' find ~/.cache/pip ~/.cache/uv -iname '*litellm*1.107.0*' 2>/dev/null
Для Docker сравните дайджест образа с заражённым sha256:1bbcb1443bc962e45f5aad53fe3d2c30600e0379715fb6c664cdd3ac126c339e:
docker images --digests | grep -i litellm
docker ps -a --format '{{.Image}} {{.CreatedAt}} {{.Names}}' | grep -i litellm
Как предупреждает LiteLLM, найденный файл подтверждает заражение, а пустой результат чистоту не доказывает: контейнеры и очистка /tmp могли всё стереть. Сам по себе sitecustomize.py ничего не значит, он бывает и в обычных установках Python. Microsoft советует сверять хэши файлов, они есть в её разборе.
Что делать, если 1.107.0 стояла
Порядок действий из поста LiteLLM:
- не начинать с
pip install: любой запуск Python в этом окружении снова выполнит вредонос; - из shell удалить
cook.pth,cook.py,sitecustomize.pyи их.pycв__pycache__из каждого затронутого site-packages; - убить фоновый процесс Python, который держит
/tmp/remote_code_loader.lock; - поставить версию с явным пином на чистую сборку, а лучше пересобрать venv и образы с нуля.
Если у машины был выход в интернет, считайте, что данные ушли. Менять нужно всё, до чего хост мог дотянуться: роль IAM инстанса, ключи провайдеров в базе прокси LiteLLM, мастер-ключ и виртуальные ключи, пароль из DATABASE_URL, любые секреты из переменных окружения. Microsoft добавляет к списку SSH- и GPG-ключи, токены CI, реестров и ИИ-инструментов, а также сессии в браузере. Ключи стоит менять с заведомо чистой машины.
Как взломали релиз LiteLLM
23 октября команда выложила подробный разбор. 27 мая в публичный репозиторий LiteLLM-Labs/lite-harness случайно закоммитили конфиг с мастер-ключом от внутреннего сервиса LiteLLM Agent Platform. Коммит быстро перезаписали, но сам ключ так и не отозвали.
- 10 октября злоумышленник зашёл с этим ключом в API сервиса и получил оттуда OAuth-токен GitHub CLI одного из владельцев организации на GitHub;
- с этим токеном он запушил временные ветки в репозитории, и их workflow отправили секреты CI на webhook.site. Так он получил токены ещё двух мейнтейнеров;
- 21 октября он в обход защиты веток поменял релизный скрипт, направив сборку на подставной Berri-AI/litellm. Запуск одобрил токеном второго мейнтейнера, а записи о запусках удалил токеном третьего.
Обычно для выпуска нужна была отдельная учётка, привязанная к одному ноутбуку в офисе. Но на той неделе правило ослабили, чтобы чаще выпускать исправления, и релиз могли запустить и одобрить любые два инженера. Подмену заметил сторонний разработчик LidorMachluf и через полчаса после выхода релиза завёл issue на GitHub.
Что взять себе
- закреплять зависимости с хэшами в lock-файлах (
uv.lock,pip install --require-hashes), а не ставить последнюю версию при каждой сборке; - в Docker указывать образ по дайджесту, а не по тегу latest;
- не давать серверам, CI и контейнерам свободный выход в интернет, если он им не нужен;
- отзывать утёкший ключ сразу, даже если коммит уже перезаписан;
- не хранить в окружении приложения лишние секреты: вредонос забирает всё, что видит процесс.
Заражённый LiteLLM провисел на PyPI меньше двух часов, но этого хватило, чтобы он попал в CI, контейнеры и на рабочие машины тех, кто ставил пакет без пина версии. Если 1.107.0 у вас была, удалять один пакет мало: нужно пересобрать окружение и сменить все ключи, которые оно видело.