
Я взял 139 имён пакетов, которые пять свежих ИИ-моделей выдумывают одинаково, и 22 сентября проверил каждое по живым PyPI и npm. Это реестры, откуда ставятся библиотеки для Python и JavaScript. Список ценен тем, что не привязан к одной нейросети: раз имя выдумывают все пять, под ударом пользователи любой из них. Список раскрыли 30 апреля 2026 года.
Занято 7 имён из 139. Под одним из них, metro-evaluator, в декабре 2025 года лежал вредоносный код: npm снял его через пять дней и поставил заглушку. Это было ещё до раскрытия списка, и совпадение это или расчёт на выдуманное имя, по реестру не видно. Остальные шесть имён заняли четыре чужих настоящих проекта, владелец бренда своей заглушкой и пустой пакет в 53 байта, который появился уже после раскрытия. По совету нейросети pip install odf поставит чей-то другой инструмент вместо нужной библиотеки.
Как галлюцинация ИИ-модели становится атакой на цепочку поставок?
Галлюцинация становится уязвимостью в один шаг: разработчик копирует из ответа нейросети команду pip install <имя> или npm install <имя> и запускает её, не проверив, существует ли пакет. Если имя свободно, его может зарегистрировать кто угодно и положить внутрь любой код. Это атака на цепочку поставок: вредоносный код приходит не напрямую, а через чужой компонент, от которого зависит проект. Код пакета запускается на машине разработчика с его правами, при установке или при первом импорте, и видит то же, что он сам: ключи, токены, переменные окружения.
Приём называют slopsquatting, по аналогии с typosquatting, где злоумышленник занимает имена с опечатками и ждёт, пока человек ошибётся при наборе. Здесь ошибается ИИ-модель, и ошибается устойчиво: у 121 имени из моего списка для PyPI набралось 3 381 появление в ответах пяти моделей.
Пустой пакет huggingface-cli на PyPI собрал больше 30 000 загрузок за три месяца. Настоящая установка выглядит как pip install -U "huggingface_hub[cli]", но на пустышку ссылалась инструкция в репозитории GraphTranslator от Alibaba, и разработчики, копировавшие команду, ставили её (Lasso Security, 2024).
В npm та же механика срабатывает через ИИ-агентов. Имя react-codeshift, склейка jscodeshift и react-codemod, попало в 47 сгенерированных файлов Agent Skills одним коммитом 17 октября 2025 года и разошлось по 237+ репозиториям. Исследователь Aikido Чарли Эриксен занял имя 14 января 2026 года, вредоносного кода туда не клал и получил около 74 загрузок в первый день, дальше по 1–4 в день. По оценке Aikido, это агенты выполняли команду из файлов (Aikido Security, 2026).
У Spracklen и коллег на 16 моделях и 576 000 фрагментах кода 19,7% рекомендованных пакетов не существовали; у коммерческих моделей в среднем 5,2%, у открытых 21,7% (USENIX Security 2025). Чурилов проверил пять ИИ-моделей, вышедших с октября 2025 по март 2026 года, и получил от 4,62% до 6,10% выдуманных пакетов. Разброс между моделями сжался, галлюцинации остались (arXiv 2605.17062).
Следующий раздел про метод и код проверки. Кому код не нужен, результаты начинаются с раздела «Из 139 выдуманных имён заняты семь», а меры защиты собраны в предпоследнем разделе.
Как я проверял 139 имён по PyPI и npm
Нейросети в проверке не участвуют: скрипт только отправляет HTTP-запросы в реестры. Список я взял из релиза v0.2-preprint репозитория Чурилова churik5/slopsquatting-replication-2026: 121 имя для PyPI и 18 для npm, те, что все пять его ИИ-моделей выдумывают одинаково. Во втором столбце файла записано, сколько раз имя встретилось в ответах. У лидера, aws-cdk, 429 раз.
Для каждого имени скрипт делает GET к pypi.org/pypi/<имя>/json или registry.npmjs.org/<имя> и сохраняет код ответа. У найденных забирает дату первой версии, число версий и признак security hold. Так называют версию-заглушку 0.0.1-security, которой npm закрывает опасное имя. Между запросами пауза 0,3 секунды, чтобы не нагружать реестр. Загрузки я брал отдельно, из api.npmjs.org/downloads и pypistats.org.
import csv, json, sys, time, urllib.error, urllib.parse, urllib.requestfrom datetime import datetime, timezoneUA = {"User-Agent": "registry-probe/0.1 (research)"}def get_json(url): req = urllib.request.Request(url, headers=UA) try: with urllib.request.urlopen(req, timeout=20) as r: return r.status, json.load(r) except urllib.error.HTTPError as e: return e.code, Nonedef probe_pypi(name): status, data = get_json(f"https://pypi.org/pypi/{name}/json") row = {"name": name, "registry": "pypi", "http": status, "exists": status == 200} if data: uploads = [f["upload_time_iso_8601"] for files in data["releases"].values() for f in files] row["first_upload"] = min(uploads) if uploads else None row["versions"] = len(data["releases"]) return rowdef probe_npm(name): quoted = urllib.parse.quote(name, safe="@") # @ember/service -> @ember%2Fservice status, data = get_json(f"https://registry.npmjs.org/{quoted}") row = {"name": name, "registry": "npm", "http": status, "exists": status == 200} if data: versions = list(data.get("versions", {})) row["created"] = data.get("time", {}).get("created") row["security_hold"] = any(v.endswith("-security") for v in versions) return rowdef main(pypi_csv, npm_csv, out): rows = [] for path, probe in ((pypi_csv, probe_pypi), (npm_csv, probe_npm)): with open(path) as f: for name, count in csv.reader(f): r = probe(name.strip()) r["times_invented"] = int(count) rows.append(r) time.sleep(0.3) with open(out, "w") as f: json.dump({"checked_at": datetime.now(timezone.utc).isoformat(), "rows": rows}, f, ensure_ascii=False, indent=1) for reg in ("pypi", "npm"): mine = [r for r in rows if r["registry"] == reg] odd = [r["name"] for r in mine if r["http"] not in (200, 404)] print(f"{reg}: {sum(r['exists'] for r in mine)} из {len(mine)} имён заняты, " f"перепроверить: {odd or 'нет'}")if __name__ == "__main__": main(*sys.argv[1:4])
Запуск: python3 probe.py pypi.csv npm.csv out.json. Поле http нужно, чтобы ответ 429 или 5xx не засчитался молча как отсутствие пакета: такие имена скрипт выводит отдельно. В моём прогоне все 132 отрицательных ответа оказались 404, и итог выглядит так: pypi: 4 из 121 имён заняты, перепроверить: нет и npm: 3 из 18 имён заняты, перепроверить: нет.
Из 139 выдуманных имён заняты семь
На 22 сентября заняты 7 имён из 139: 4 из 121 на PyPI и 3 из 18 на npm. Остальные 117 и 15 отвечают 404. Шесть занятых имён появились в реестрах ещё до раскрытия списка 30 апреля 2026 года, после него появилось одно, css-color-stop.
|
Имя |
Реестр |
Первая версия |
Кто занял и что внутри |
Загрузки |
|
|
PyPI |
сентябрь 2024 |
чужой проект: self-hosted клиент iMessage, 50 версий |
108 в месяц |
|
|
PyPI |
август 2024 |
чужой проект: утилиты для Open Data Framework, в том числе сервер для ИИ-агентов, 2 версии |
4 932 в месяц |
|
|
PyPI |
октябрь 2024 |
заглушка владельца бренда, держит имя (настоящий клиент называется |
нет данных |
|
|
PyPI |
январь 2026 |
чужой проект: среда выполнения на C с обёрткой для Python, 7 версий |
52 в месяц |
|
|
npm |
7 декабря 2025 |
заглушка npm после вредоноса: четыре версии сняты 12 декабря, сейчас |
12 за месяц, пик 25 в мае |
|
|
npm |
ноябрь 2025 |
чужой проект: утилита для переключения SSH-ключей, 2 версии |
19 в месяц |
|
|
npm |
28 июля 2026 |
пустышка: версия |
102 в день регистрации, дальше 0–7 в день |
Раскладка такая: четыре чужих настоящих проекта (lusid, odf, usr, ssh-keys), одна заглушка владельца бренда (weaviate), одна заглушка npm на месте вредоносного пакета (metro-evaluator) и одна пустышка (css-color-stop). Сейчас вредоносного кода нет ни под одним из семи имён. Под metro-evaluator он был.
Под выдуманным именем metro-evaluator уже лежал вредонос
metro-evaluator все пять ИИ-моделей из работы Чурилова выдумали в сумме 10 раз. 7 декабря 2025 года под этим именем опубликовали четыре версии, с 1.0.0 по 1.0.3, между 05:41 и 06:09 UTC, меньше чем за полчаса. 12 декабря их не стало, и npm поставил заглушку 0.0.1-security с таким README (registry.npmjs.org/metro-evaluator):
> This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
В переводе: «Пакет содержал вредоносный код и был удалён из реестра командой безопасности npm. Заглушка опубликована, чтобы пользователи не пострадали в будущем». Что именно делал код, из реестра не видно. Занял ли кто-то имя потому, что его выдумывают нейросети, или просто совпал с ним, тоже не видно. В обзоре Socket от 22 июля 2026 года metro-evaluator помечен как имя, которое уже закрыто заглушкой security hold (Socket, 2026).
Заглушку продолжают пытаться ставить. С января 2026 года у неё не было месяца без загрузок: 4 в январе, 4 в феврале, 3 в марте, 9 в апреле, 25 в мае, 12 в июне, 13 в июле, 10 в августе и 6 за сентябрь по 21-е число. Пик пришёлся на май, первый месяц после раскрытия списка; связаны ли эти события, по одному счётчику не понять.
Похожий случай описала Aikido: вредоносный npm-пакет unused-imports, у которого настоящий аналог называется eslint-plugin-unused-imports. Пакет уже закрыли заглушкой security hold, а в начале февраля 2026 года имя всё ещё получало около 233 загрузок в неделю: это попытки поставить то, чего в реестре уже нет (Aikido Security, 2026). Заглушка защищает тех, кто придёт после неё. Тем, кто поставил пакет между 7 и 12 декабря, она уже не помогла.
Пустой пакет в 53 байта скачивают примерно через день
Имя css-color-stop заняли 28 июля 2026 года, почти через три месяца после публикации списка, и внутри пакета нет ничего. Версия 0.0.0, один файл package.json на 53 байта, ни установочных скриптов, ни зависимостей, ни описания, ни лицензии. Это похоже на бронирование имени. На атаку пока не похоже: исполнять там нечего.
В день регистрации у пакета 102 загрузки, на следующий 18, дальше от 0 до 7 в день. Из 56 дней с 28 июля по 21 сентября загрузок не было в 27, в сентябре в 12 днях из 21, а с 15 по 21 сентября ни одной. За июль набралось 130 загрузок, за август 53, за сентябрь по 21-е число 17.
Кто эти загрузки делает, я не знаю. Счётчик npm включает зеркала и сканеры безопасности, а они скачивают новые пакеты. Поэтому всплеск в первый день и хвост в единицы загрузок бывают и у пакета, который никто не ставил. У react-codeshift форма графика похожая, но там источник установок известен: 237+ репозиториев с командой npx. У css-color-stop README нет, источник установок неизвестен, и по сходству формы нельзя сказать, что его ставят люди или агенты.
По совету нейросети pip install odf ставит чужой проект
Четыре имени из списка выдуманных, lusid, odf, usr и ssh-keys, заняты настоящими проектами, которые к подсказке нейросети отношения не имеют. Автор репозитория сам предупреждает в README: сверка шла по старому снимку реестров из работы Spracklen, и имена, зарегистрированные позже, засчитаны как несуществующие. Скорее всего, так в список попали все четыре, а с ними weaviate и metro-evaluator: оба зарегистрированы уже после снимка. Для шести имён из семи список Чурилова устарел, и это поправка к нему, а не находка про slopsquatting. Socket в своём разборе так же снял ssh-keys как существующий пакет.
Для разработчика поправка всё равно важна. Пакет odf на PyPI — утилиты и сервер для проекта Open Data Framework, последняя версия вышла 6 сентября 2026 года, загрузок 4 932 в месяц. Библиотека для документов OpenDocument ставится как odfpy, а в коде подключается через import odf: внутри неё лежит модуль odf. Вероятно, нейросеть путает имя модуля с именем пакета, и разработчик по её совету получает чужой инструмент. Вредоносного в нём ничего нет, но это не та библиотека. Сколько из 4 932 загрузок в месяц пришли так, из открытых данных не видно.
С lusid то же самое. Под этим именем лежит self-hosted клиент iMessage, а SDK финансовой платформы LUSID называется lusid-sdk. weaviate держит сам владелец бренда пустой заглушкой, настоящий клиент называется weaviate-client, и эта заглушка безопасна.
Ответ 404 ещё не значит, что имя свободно
Код 404 говорит только о том, что пакета с таким именем сейчас нет. Зарегистрировать его можно не всегда: по данным Чурилова, 68 из 109 его PyPI-имён уже не занять. Реестр держит их в списке запрещённых или отсекает ультранормализацией, то есть сверкой похожих написаний вроде aws_cdk и aws-cdk. После всех защит реестров, по подсчёту Чурилова и Socket, доступными для регистрации остаются 53 имени. Проверять это на своих 117 именах регистрацией я не стал: это и был бы тот самый сквоттинг.
Частота выдумывания и опасность имени связаны слабо. aws-cdk ИИ-модели предлагали 429 раз, чаще любого другого имени, но PyPI держит его в запрете. На npm лидеры списка, @ember/service (55 раз) и другие @ember/, по разбору Socket оказались настоящими импортами фреймворка Ember, которые разрешаются через ember-source, а не отдельными пакетами. metro-evaluator с его 10 появлениями стоит в том же списке ниже. Опасно свободное имя, которое пока никто не занял, и такие в списке есть; называть их я не буду, это готовый список целей.
Что делать, если нейросеть для программирования сама ставит зависимости
Под одним именем из списка вредонос уже был, свободное имя можно занять в любой день, а установка по совету ИИ для программирования обычно идёт без проверки. Код пакета выполняется с правами разработчика и видит его ключи и токены, поэтому первый пункт касается не отдельного программиста, а тимлида. У каждой меры своя зона: одни ловят первую установку выдуманного имени, другие только подмену того, что уже одобрено.
-
Решить на уровне команды, может ли ИИ-агент ставить зависимости без подтверждения человека. Защищает от того, что агент выполнит выдуманную команду без человека: команду
npx react-codeshiftиз 237+ репозиториев выполняли как раз агенты. Подсунуть агенту имя можно и через контент, который он читает: у группы Бена Насси из Тель-Авивского университета с коллегами из Техниона и Intuit доля запросов к репозиторию, где ассистент выдумывал подсказанное имя, доходила до 85%, а при установке навыка, готового набора инструкций для агента, была 100%. Атаку проверили на Cursor, Windsurf, GitHub Copilot, Cline и Gemini CLI (The Hacker News, 2026). -
Смотреть на пакет до установки, не скачивая его обычным способом. Для npm это
npm view <имя> time: команда показывает даты всех версий, и уmetro-evaluatorтам видны четыре версии 7 декабря и заглушка 12 декабря. Для PyPI то же отдаётhttps://pypi.org/pypi/<имя>/json. Если нужно открыть сам пакет, тоpip download --only-binary :all: --no-deps <имя> -d /tmp/x. Без--only-binarypip для пакета, у которого есть только архив исходников, запускает егоsetup.pyещё при скачивании. Защищает от первой установки выдуманного имени, то есть от основного сценария этой статьи. Свежая дата у библиотеки, которую вы считали давно известной, повод остановиться. Старая дата не гарантирует, что пакет тот:odfживёт на PyPI с 2024 года и всё равно не OpenDocument, это видно только по описанию. -
Ставить незнакомое с
npm install --ignore-scripts. Флаг не даёт пакету выполнить свой код при установке. От кода, который сработает при первом импорте, он не защищает. -
Держать lock-файл и хэши. Lock-файл записывает точные версии зависимостей,
pip install --require-hashesсверяет каждый пакет с его хэшем. Защищает от подмены уже одобренного пакета: если под знакомым именем появится другой код, установка упадёт. От первой установки выдуманного имени не защищает, хэш закрепит ровно тот пакет, что поставили по совету нейросети. -
Поставить закрытый прокси-реестр с белым списком. Команда ставит только одобренные пакеты, и подсказка нейросети с незнакомым именем упирается в отказ реестра. Защищает и от первой установки, но только пока в белый список имя вносит человек, который его проверил.
Ограничения проверки 139 имён по реестрам
Это разовый срез на 22 сентября 2026 года, и состав занятых имён через полгода наверняка будет другим. Я проверял файл из релиза v0.2-preprint как есть, со 139 именами; в итоговой версии препринта Чурилов сократил список до 127 (109 для PyPI и 18 для npm). По weaviate загрузок нет: pypistats на запрос ответил ошибкой 429. Что лежало в четырёх снятых версиях metro-evaluator, по реестру не восстановить, видны только даты публикации.
ссылка на оригинал статьи https://habr.com/ru/articles/1087738/