Слопсквоттинг в npm и PyPI. Как галлюцинации ИИ превращаются в supply-chain атаку

1735
Слопсквоттинг в npm и PyPI. Как галлюцинации ИИ превращаются в supply-chain атаку

В обычной галлюцинации ИИ нет ничего особенно страшного. Модель написала pip install some-smart-library, PyPI ответил, что такого пакета нет, сборка упала. Слопсквоттинг начинается на следующем шаге. Кто-то заранее регистрирует выдуманное моделью имя и публикует под ним собственный пакет. Теперь ошибочная команда выполняется без предупреждения и разработчик получает уже настоящий чужой код.

Меня в этой схеме больше всего беспокоят ИИ-агенты. Человек хотя бы может удивиться незнакомому названию. Агенту дали терминал, доступ к npm или PyPI и право исправлять проект самостоятельно. Галлюцинация превращается из ошибки текста в действие. Поэтому проверять нужно не только сгенерированный код, но и сам факт появления каждой новой зависимости.

Материал посвящен защите цепочки поставок ПО. Проверять подобные сценарии следует только в собственных тестовых средах. Нельзя применять описанные механизмы для публикации вредоносных пакетов, несанкционированного доступа или атак на чужую инфраструктуру. Нужно соблюдать правила сервисов и законодательство своей страны, включая российское.

Где заканчивается галлюцинация и начинается атака

Package hallucination и slopsquatting часто смешивают, хотя это разные события. Галлюцинация возникает, когда модель рекомендует пакет, которого нет в нужном реестре. Слопсквоттинг требует еще одного участника. Кто-то должен занять выдуманное имя и рассчитывать, что будущий пользователь или агент установит уже зарегистрированную библиотеку.

От классического typosquatting схема отличается источником ошибки. При тайпосквоттинге человек хотел написать requests, ошибся в названии и попал на похожий пакет. При слопсквоттинге человек может идеально скопировать команду. Неверное имя придумала модель.

LLM легко собирает правдоподобные названия из знакомых шаблонов. Модель может склеить два настоящих проекта, поменять суффикс, перенести имя из другой экосистемы или придумать библиотеку, которая логично звучит в контексте задачи. Само наличие веб-поиска или инструментов у агента проблемы не решает. Защита появляется только тогда, когда система обязана независимо проверить происхождение зависимости перед установкой.

Базовая цепочка выглядит так.

  1. ИИ предлагает зависимость. Имя выглядит естественно, но в npm или PyPI пакета нет.
  2. Галлюцинация повторяется. Для атакующего ценнее имена, которые разные запросы или модели воспроизводят снова.
  3. Свободное имя регистрируют. С этого момента проверка «существует ли пакет» уже ничего не гарантирует.
  4. Человек или агент повторяет команду. Менеджер пакетов видит нормальную запись реестра и загружает содержимое.
  5. Чужой код получает шанс выполниться. В npm риск связан в том числе с lifecycle-скриптами и запуском CLI через npx. В Python код может выполняться во время сборки исходного дистрибутива, а содержимое установленной библиотеки сработает позже при импорте или вызове функций.

Поэтому полезная быстрая проверка выглядит так.

npm view PACKAGE_NAME name version time maintainers repository --json
 
 python -m pip index versions PACKAGE_NAME

Команды отвечают только на вопрос, что сейчас лежит в реестре. Молодой пакет без истории, с неожиданным владельцем и несколькими релизами за один день требует отдельной проверки. Старый и популярный пакет тоже нельзя автоматически считать безопасным, поскольку учетные записи мейнтейнеров и сами проекты периодически компрометируют.

Не каждый пятый ответ ИИ содержит выдуманный пакет

Самая популярная цифра по теме звучит пугающе. Почти 20% пакетов, рекомендованных моделями в крупном эксперименте, оказались выдуманными. Формулировку часто сокращают до «20% кода ИИ содержит фальшивые зависимости», а это уже неверно.

В получившем награду Distinguished Paper исследовании USENIX авторы протестировали 16 моделей и получили 576 тысяч образцов Python- и JavaScript-кода. Из ответов извлекли около 2,23 млн рекомендаций пакетов. 440 445 рекомендаций, или 19,7%, указывали на несуществующие зависимости. Уникальных выдуманных имен оказалось 205 474.

Знаменитые 19,7% относятся именно к рекомендациям пакетов в конкретной экспериментальной выборке. Цифра не означает, что каждый пятый современный ответ ChatGPT, Claude или Gemini обязательно содержит опасную библиотеку. Модели из первоначальной выборки к нынешнему моменту уже нельзя считать текущим срезом рынка.

Что измеряли Результат Как читать цифру
Большая работа USENIX на 16 моделях 19,7% рекомендаций пакетов признали галлюцинациями Исторический большой эксперимент, а не универсальная вероятность для любого современного ИИ
Коммерческие модели в той же работе В среднем 5,2% Заметно меньше среднего по всей выборке
Открытые модели в той же работе В среднем 21,7% Разрыв между группами сильно повлиял на общую цифру
Свежая репликация на пяти более новых моделях Примерно 4,62–6,10% Проблема стала реже, но не исчезла

Старая работа показала еще одну неприятную особенность. Исследователи повторили запросы, которые уже породили галлюцинацию. В 43% случаев исходное вымышленное имя появлялось во всех десяти повторных запросах, еще часть имен возвращалась несколько раз. Для атакующего предсказуемая ошибка гораздо ценнее случайного набора символов. Можно искать названия, которые модель упорно воспроизводит, и ждать следующего пользователя.

Свежая репликация картины не опровергла, но заметно уменьшила масштаб. На выборке из 199 845 ответов пяти более новых моделей исследователь получил диапазон около 4,62–6,10%. Работу пока следует воспринимать как препринт, а не как окончательный отраслевой бенчмарк.

На мой взгляд, самое интересное в свежем эксперименте не проценты. Пять разных моделей независимо придумали одни и те же 127 названий пакетов. После проверки ограничений PyPI и npm 53 имени оставались доступными для регистрации. Такой результат разрушает удобный аргумент «у каждой модели свои случайные ошибки». По крайней мере часть галлюцинаций пересекается между моделями, поэтому одно занятое имя потенциально работает против пользователей разных ИИ-систем.

Одновременно не стоит делать следующий логический прыжок и объявлять сотни тысяч выдуманных названий уже существующей армией вредоносных пакетов. Исследование USENIX измеряло поверхность атаки, а не число реальных заражений. Авторы сознательно не публиковали пакеты под найденными именами. В открытых данных пока нет оснований утверждать, что злоумышленники массово регистрируют все популярные галлюцинации и получают соответствующее число компрометаций. Риск технически доказан, реальный масштаб эксплуатации изучен заметно хуже.

PyPI и npm уже показали, как ошибка начинает жить самостоятельно

Один из лучших практических примеров связан с huggingface-cli. Исследователь Бар Ланьядо обнаружил, что модели повторяют такое имя в командах pip install, и зарегистрировал пустой безопасный пакет в PyPI. Эксперимент не был атакой, исследователь хотел понять, станет ли кто-нибудь действительно устанавливать выдуманную зависимость.

С подсчетом загрузок есть полезная для фактчекинга деталь. Первые независимые публикации говорили более чем о 15 тысячах скачиваний и прямо предупреждали, что нельзя определить, сколько установок пришло именно из ответов ИИ. Позднее Lasso после фильтрации автоматических сканеров оценивала результат более чем в 30 тысяч загрузок за три месяца. Поэтому я бы не писал «30 тысяч разработчиков установили пакет по совету ChatGPT». Такой вывод данные не позволяют сделать.

Гораздо показательнее другое. Команда с huggingface-cli попала в публичные GitHub-репозитории, включая проект GraphTranslator, связанный с исследователями Alibaba. Галлюцинация перестала зависеть от конкретного диалога с моделью. Ошибочную команду могли копировать из уже опубликованной документации. Получился своеобразный механизм самораспространения, где ИИ породил ошибку один раз, а дальше ее размножали обычные репозитории и инструкции.

Еще нагляднее история react-codeshift в npm. Название выглядит почти идеально, поскольку существуют реальные проекты jscodeshift и react-codemod. Модель фактически собрала из двух знакомых имен третье.

Исследователь Aikido Чарли Эриксен обнаружил ссылку npx react-codeshift в инструкциях для ИИ-агентов. К моменту проверки несуществующая команда уже распространилась через форки и копии в 237 GitHub-репозиториев. Эриксен занял свободное имя защитным пустым пакетом и после публикации увидел реальные обращения к нему. Случай подробно показывает, почему агентная разработка опаснее обычного чата с моделью. В файле навыка или инструкции галлюцинация сохраняется надолго, а npx способен не просто скачать пакет, но сразу запустить его CLI.

Есть и более тревожный npm-пакет unused-imports. Легитимный ESLint-плагин называется eslint-plugin-unused-imports, тогда как пакет с коротким именем был признан вредоносным и связан с кампанией PhantomRaven. Опубликованный код похищал токены и учетные данные. Здесь, однако, я бы не ставил красивую галочку «первая доказанная атака slopsquatting». Aikido тоже формулирует вывод осторожно. Вредоносность unused-imports подтверждена, модели действительно способны рекомендовать похожее имя, но публичных доказательств, что оператор вредоноса специально выбрал название после изучения галлюцинаций ИИ, нет.

Такую границу полезно сохранять и в других публикациях. Безопасная регистрация huggingface-cli и react-codeshift доказала работоспособность механики. unused-imports доказал, что подходящее имя может вести к настоящему вредоносу. Ни один из трех случаев сам по себе не доказывает массовую криминальную кампанию слопсквоттинга. Ранние исследования и разбор SecurityLab хорошо показывают сам класс угрозы, но нынешние данные позволяют описывать его точнее и без лишней сенсационности.

Тем временем идея уже вышла за пределы npm и PyPI. В свежей работе по HalluSquatting исследователи проверили, способны ли агенты галлюцинировать не пакеты, а имена репозиториев и устанавливаемых навыков. В отдельных контролируемых сценариях частота неправильных идентификаторов доходила до 85% при клонировании репозитория и до 100% при поиске навыка. После регистрации таких ресурсов исследователи добивались выполнения инструментов и кода в нескольких агентных приложениях с терминалом.

Цифры 85% и 100% нельзя переносить на обычное программирование. Они относятся к специально построенным экспериментальным сценариям и не означают «100% агентов можно взломать». Работа показывает более общую проблему. Агент способен галлюцинировать любой внешний идентификатор, а затем самостоятельно пойти за выдуманным ресурсом.

Я бы блокировал новую зависимость до первого npm install

Просьба «проверяйте пакеты внимательнее» плохо масштабируется. Для компании нужен технический запрет. ИИ может предложить библиотеку, разработчик может добавить ее в pull request, но неизвестное имя не должно автоматически попадать из публичного реестра на рабочую станцию или CI.

Самая надежная схема строится из нескольких барьеров.

  • Единый корпоративный источник пакетов. Рабочие станции, CI и ИИ-агенты не ходят напрямую в npmjs.org и PyPI.
  • Allowlist новых зависимостей. Пакет сначала проверяют, затем разрешают его имя и конкретную версию или артефакт.
  • Запрет прямого выхода в публичные реестры. Иначе агент просто обойдет внутренний URL своим npm install или pip install.
  • Отдельный review изменений manifest и lock-файлов. Новый package.json, requirements.txt, pyproject.toml или lock-файл должен считаться изменением границы доверия.
  • Минимальные права агента. Терминал без боевых токенов и секретов, сетевые ограничения, отдельная песочница и ручное подтверждение новых внешних компонентов.

Сам по себе корпоративный proxy проблему не закрывает. Если Artifactory, Nexus или другой прокси автоматически скачивает любое неизвестное имя из публичного реестра, слопсквоттинг спокойно пройдет через такой кэш. Нужен режим, при котором неизвестный компонент сначала блокируется, а человек или отдельный сервис проверки явно разрешает импорт.

Для npm положение заметно улучшилось. В npm 12 lifecycle-скрипты зависимостей preinstall, install и postinstall по умолчанию больше не запускаются без разрешения. Автоматическое получение Git-зависимостей и удаленных архивов тоже стало opt-in. Политику разрешенных install-скриптов можно хранить в проекте через allowScripts.

npm approve-scripts --allow-scripts-pending
 npm ci

Но npm 12 не является вакциной от слопсквоттинга. Менеджер все равно может скачать вредоносный пакет. Код библиотеки может выполниться позже при обычном использовании. Еще опаснее одноразовые команды вроде npx some-package или npm exec some-package. Смысл таких команд как раз состоит в запуске CLI пакета. Поэтому агентам я бы запрещал произвольный npx, bunx, pnpm dlx и аналогичные команды без отдельного разрешения имени.

npm ci тоже часто переоценивают. Команда хорошо воспроизводит содержимое lock-файла, но не отвечает на вопрос, безопасны ли зафиксированные зависимости. Если ИИ добавил вредоносный пакет одновременно в package.json и package-lock.json, npm ci честно установит именно его. Поэтому проверка новых имен должна выполняться до установки.

Минимальный gate для небольшого проекта можно сделать даже простым скриптом.

const fs = require('fs');
 const pkg = require('./package.json');
 
 const approved = new Set(
   fs.readFileSync('approved-packages.txt', 'utf8')
     .split(/r?
/) .map(x => x.trim()) .filter(Boolean) ); const dependencies = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.optionalDependencies }; const unknown = Object.keys(dependencies) .filter(name => !approved.has(name)); if (unknown.length) { console.error('Blocked unknown packages ' + unknown.join(', ')); process.exit(1); }

Скрипт не ищет вредоносы и не заменяет SCA. Задача проще. Незнакомое имя физически не проходит в сборку, пока команда не добавит его в список после проверки. В большой инфраструктуре такую политику лучше переносить на корпоративный registry и admission gate, чтобы разработчик или агент не мог удалить проверку одним коммитом.

В Python я бы строил еще более жесткий контур. Проверенные wheels заранее попадают во внутреннее хранилище, а production-сборка вообще не видит публичный PyPI.

python -m pip install 
   --no-index 
   --find-links=/srv/wheelhouse 
   --require-hashes 
   --only-binary=:all: 
   -r requirements.txt

--no-index запрещает поиск в package index, --find-links указывает на подготовленное хранилище, --require-hashes требует заранее известных хешей всех зависимостей, а --only-binary=:all: запрещает source distributions. Последнее ограничение снижает риск выполнения произвольного build-кода при создании пакета из исходников.

Хеши тоже нельзя превращать в магический амулет. SHA-256 подтверждает, что сборка получила тот же файл, который заранее одобрили. Хеш ничего не говорит о безопасности самого файла. Если команда изначально зафиксировала вредоносный wheel, --require-hashes прекрасно и воспроизводимо установит вредоносный wheel.

Отдельно я бы избегал бездумной схемы с внутренним Python-репозиторием плюс публичным PyPI через --extra-index-url. pip рассматривает доступные источники совместно и выбирает подходящий вариант, а не считает первый index безусловно более доверенным. Такой дизайн создает уже классический риск dependency confusion. Безопаснее дать сборочной среде один контролируемый источник.

Главный вывод для меня не в том, что ИИ «слишком глупый и выдумывает библиотеки». Ошибки моделей постепенно становятся реже. Проблема возникает, когда вероятностный текст напрямую соединяют с менеджером пакетов и терминалом, а внешнему имени автоматически присваивают доверие.

Я бы ввел простое правило. Любая новая зависимость, которую предложил человек, Copilot, Claude, ChatGPT или автономный агент, сначала считается неизвестным внешним кодом. Существование пакета проверяем отдельно, происхождение и владельца изучаем, версию и артефакт фиксируем, а установка из сети разрешается только после такого допуска. Тогда даже очень убедительная галлюцинация заканчивается отказом policy gate, а не новым процессом на машине разработчика.

Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • Москва • 15 октября • ● 15.1015.10Большая играЕжегодная IT-конференция Orion SoftРегистрация→Реклама. 18+ ООО «Орион» ИНН 9704113582

Юрий Кочетов

Здесь я делюсь своими не самыми полезными, но крайне забавными мыслями о том, как устроен этот мир. Если вы устали от скучных советов и правильных решений, то вам точно сюда.

Рекламодатель
Реклама. АО «Позитив Текнолоджиз». ИНН 7718668887. 18+
Сайт рекламодателя: ptsecurity.com↗
АО «Позитив Текнолоджиз»