Дві версії LiteLLM — 1.82.7 і 1.82.8 — потрапили на PyPI уже із вшитим бекдором. Якщо у вас у проді крутилася бодай одна з них, ця supply chain атака тихо віддала ключі від вашого AI-пайплайна людині, якої ви ніколи не бачили: змінні оточення, API-ключі, хмарні креди — усе разом. LiteLLM — це шлюз, через який тисячі команд ганяють запити до OpenAI, Anthropic, AWS Bedrock та Azure OpenAI одним API. Саме це охоплення й зробило історію такою болючою.

Розберімо по черзі: що сталося, як працював бекдор і що б я зробив ще до обіду, якби знайшов ці версії у себе в локфайлах.

Що сталося насправді

LiteLLM — найпопулярніший AI-шлюз на ринку. Сотні моделей, десятки провайдерів, один API. Він сидить посередині вашого inference-стека і за визначенням тримає в себе всі креди, які ви йому згодовуєте, — бо в цьому вся його робота.

Атакувальник не шукав хитрий zero-day у коді. Він зайшов в акаунт мейнтейнера на PyPI. Без двофакторки. Один акаунт, один пароль — і тієї ж миті, коли це впало, він міг публікувати будь-що під іменем, якому вже довіряли тисячі requirements.txt. Він виклав 1.82.7 і 1.82.8. Усі, у кого в цьому вікні відпрацював незапінений pip install litellm, затягнули шкідливий код просто в прод.

Як працював бекдор

Корисне навантаження було нудним — і саме тому дієвим. Скомпрометовані версії читали оточення — те саме, де живуть ваші OPENAI_API_KEY, ANTHROPIC_API_KEY, креди AWS і секрети Azure, — і відправляли їх на endpoint атакувальника.

Жодного падіння. Жодної помилки в логах. Жодної зміни поведінки, за яку зачепився б моніторинг. Шлюз як маршрутизував запити, так і продовжував — і паралельно виносив ваші секрети через чорний хід. Коли хтось це помітив, ключів уже не було.

Що має налякати вас по-справжньому

Це вже третя велика supply chain атака на PyPI за рік, і малюнок завжди той самий:

  • Популярний пакет з єдиним мейнтейнером
  • Акаунт під захистом одного пароля і більше нічого
  • Мільйони встановлень нижче по ланцюгу, які за години успадковують шкідливу версію
  • Команди, які ніколи не перевіряють, що саме вони затягнули

Більшість так нічого й не помічає — ось де справжня проблема. Якщо ваш pip install крутиться в CI без перевірки хешів, ви довіряєте гігієні чужого акаунта свої прод-ключі. Ось і вся модель безпеки, якщо називати речі своїми іменами.

Що робити просто зараз

Якщо LiteLLM у вас є бодай десь — зробіть це сьогодні, по черзі:

  1. Перевірте локфайли. Якщо стоїть 1.82.7 або 1.82.8 — негайно відкочуйтеся на 1.82.6.
  2. Ротуйте всі креди, які бачив LiteLLM. Кожен ключ провайдера, кожен хмарний секрет, кожен токен із цього оточення. Вважайте, що витекло все, — бо так і є.
  3. Перевірте вихідний трафік. Підніміть логи кластера й шукайте з'єднання з endpoint'ами, яких ви не впізнаєте. Це і є канал витоку.
  4. Додайте перевірку хешів у CI/CD. Пінюйте точні версії й звіряйте контрольні суми на кожному pip install. Не зійшовся хеш — білд падає. Без варіантів.

Ротація ключів — та сама нудна й марудна частина, яку всі хочуть пропустити. Не пропускайте. Якщо шкідлива версія відпрацювала бодай раз із доступом до креда — цей кред скомпрометований.

Ваш пайплайн настільки безпечний, наскільки його найслабша залежність

Я веду 62 підписки Azure у проді. Я на власні очі бачив, як одна погана залежність розходиться каскадом по всьому оточенню, — і це не те, що хочеться дебажити о другій ночі. Саме тому я пінюю кожну залежність і звіряю кожну контрольну суму, без винятків. Як Certified Ethical Hacker я привчив себе думати як атакувальник — а атакувальник не ламає ваш код, він ламає довіру, яку ви роздаєте всім, хто стоїть вище за вас у ланцюгу.

«Просто pip install» здається безкоштовним. Це не так. Зручність — це кредит під заставу вашої безпеки, а supply chain атаки — це рівно той момент, коли приходить рахунок. Кожна незапінена залежність — це чужа людина з копією ваших прод-ключів, яка просто поки що не вирішила ними скористатися.

Ваш AI-пайплайн настільки безпечний, наскільки його найслабша залежність. Цього тижня такою залежністю виявився LiteLLM. Наступного буде щось інше. Тож чесне питання: коли ви востаннє реально перевіряли цілісність своїх Python-залежностей?