За первую половину 2026 года в npm случилось три громких взлома. В ночь на 31 марта атакующий выпустил вредоносные версии axios — HTTP-клиента, у которого, по счётчику Snyk, больше 100 миллионов загрузок в неделю. 11 мая заражённые релизы получили 42 пакета TanStack. 17 июня под удар попали больше 140 пакетов Mastra — фреймворка для ИИ-агентов.
Защита от таких атак складывается из нескольких инженерных привычек, которые настраивают один раз. Ниже разбираем, как устроены эти три истории и какие настройки снижают риск в проектах на Node.js и Python.
Почему npm стал такой удобной мишенью
Проект на Node.js состоит из твоего кода и чужих пакетов, которые подтягиваются автоматически. Ставишь один фреймворк — он приводит за собой десятки зависимостей, у каждой из них есть свои. Запусти npm ls --all и посмотри на всё дерево: почти всех его авторов ты никогда не видел.
Каждый из этих людей — потенциальная точка входа. Атакующему не нужно ломать твой проект напрямую. Достаточно получить контроль над одним популярным пакетом где-то в глубине дерева, и его код окажется на машинах всех, кто этот пакет использует, прямо или через другие зависимости.
Такие атаки называют атаками на цепочку поставок (supply chain attacks). Удар приходится по звену, которому проект доверяет: чужому пакету, аккаунту его автора или конвейеру, который этот пакет собирает.
Как именно это происходит
За инцидентами 2026 года стоят несколько типичных сценариев.
Угон аккаунта мейнтейнера. У человека, который поддерживает популярный пакет, крадут доступ: через фишинг, утечку токена npm или повторно использованный пароль. Затем атакующий публикует новую версию от его имени. Так случилось с axios. В ночь с 30 на 31 марта 2026 года атакующий, получивший доступ к npm-аккаунту ведущего мейнтейнера, вручную опубликовал украденным токеном версии 1.14.1 и 0.30.4 (разборы Sophos и Endor Labs). В них добавилась зависимость plain-crypto-js, чей postinstall-скрипт ставил троян удалённого доступа (RAT) под macOS, Windows и Linux. npm удалил обе версии примерно через три часа.
Обе версии были патч-релизами: 1.14.1 вышла следом за 1.14.0, 0.30.4 — следом за 0.30.3. Диапазоны вида ^1.14.0 и ~1.14.0 в package.json пропускают такие обновления, поэтому новая установка или npm install без lock-файла в эти часы могли подтянуть заражённую версию.
Отравление CI и кража токена публикации. История с TanStack сложнее. 11 мая 2026 года с 19:20 до 19:26 UTC вышли 84 вредоносные версии в 42 пакетах, по две на пакет (Snyk). Аккаунты мейнтейнеров при этом никто не взламывал (The Register). Атакующий воспользовался ошибкой в настройке GitHub Actions, которую называют Pwn Request: воркфлоу на событие pull_request_target забирал код из форка и запускал его в контексте основного репозитория. Этот код отравил кэш сборки, релизный воркфлоу потом восстановил его, и вредоносная программа достала из памяти раннера OIDC-токен, которым CI публикует пакеты в npm. Релизы выпустил легальный конвейер TanStack, поэтому со стороны всё выглядело как обычное обновление.
Typosquatting и выдуманные имена. Атакующий регистрирует пакет с названием, похожим на популярное: переставляет буквы, убирает дефис или добавляет -js. Одна опечатка в package.json или в команде установки — и вместо настоящей библиотеки приходит подделка. Зависимости из историй с axios и Mastra устроены так же: plain-crypto-js выдавал себя за crypto-js, easy-day-js — за dayjs.
С ИИ-ассистентами у этой схемы появился вариант, его называют slopsquatting. Модель советует пакет, которого не существует, а атакующий заранее регистрирует это имя. В исследовании, принятом на конференцию USENIX Security 2025, модели сгенерировали 576 000 примеров кода на Python и JavaScript. Доля выдуманных пакетов составила в среднем не меньше 5,2% у коммерческих моделей и 21,7% у открытых.
Установочные скрипты. npm умеет запускать скрипты пакета прямо во время установки, ещё до старта твоего кода. Для легальных задач это удобно, например для сборки нативного модуля. Этим же каналом прошли все три атаки 2026 года: в axios и Mastra вредоносный код запускал postinstall, в TanStack — хук prepare. Вредоносный код TanStack, по данным Snyk, собирал учётные данные GitHub Actions, AWS, Kubernetes и npm, а также SSH-ключи.
С Mastra схема была двухходовой. 16 июня в npm появился пакет easy-day-js, а ночью 17 июня его вредоносную версию добавили зависимостью в пакеты @mastra/*. Их опубликовали с угнанного аккаунта участника проекта, у которого были права на публикацию (The Hacker News, Socket). В Socket предупреждают: программа прописывает себя в автозагрузку, поэтому удалить пакет или папку node_modules недостаточно.
Что снижает риск
Полной защиты нет: подключая чужой код, ты берёшь на себя часть чужого риска. Поверхность атаки при этом можно заметно уменьшить несколькими привычками.
Храни lock-файл в репозитории и ставь зависимости через npm ci. package-lock.json коммитится и проходит ревью вместе с кодом. На CI и в проде используй npm ci. По документации npm эта команда требует готовый lock-файл, перед установкой удаляет node_modules, ничего не пишет ни в package.json, ни в lock-файл и завершается с ошибкой, если они расходятся. Обычный npm install в такой ситуации может тихо подтянуть другую версию, и найти её потом сложно.
Не обновляйся в день релиза. Вредоносные версии axios и TanStack провисели в реестре около трёх часов. Если ставить свежие версии через несколько дней после выхода, заражённый релиз к этому моменту успеют найти и удалить. Поэтому автообновление зависимостей на проде без паузы опасно.
Отключай установочные скрипты. С флагом --ignore-scripts npm не запускает скрипты из package.json пакетов. Некоторые зависимости без них не соберутся, потому что компилируют нативный код. Поэтому на CI флаг включают по умолчанию, а скрипты разрешают точечно, только тем пакетам, которым они нужны.
Сокращай число зависимостей. Каждая библиотека, подключённая ради одной функции, — это новые мейнтейнеры, которым ты доверяешь свою систему. Иногда проще написать двадцать строк самому, чем тянуть пакет с полусотней зависимостей. Число зависимостей пакета видно на его странице на npmjs.com: проверь его до установки.
Проверяй происхождение пакетов. npm поддерживает provenance — подтверждение того, что пакет собран в публичном CI из конкретного коммита. Обычные релизы axios выходят с ним, у вредоносных версий его не было (Endor Labs), так что при угоне аккаунта provenance помогает заметить подделку. От угона самого конвейера он не спасает: вредоносные релизы TanStack получили валидные подтверждения уровня SLSA Build Level 3, и Snyk называет это первым задокументированным случаем такого рода. Проверить подписи и подтверждения установленных пакетов можно командой npm audit signatures (документация). Если публикуешь свои пакеты, provenance стоит включить.
Разделяй токены и урезай им права. Токен для публикации в npm должен лежать там, где до него трудно добраться, и уметь только то, что нужно для публикации. Отдельные токены с минимальными правами и коротким сроком жизни выглядят скучно, но вредоносные версии axios опубликовали именно украденным токеном. Если пакеты публикует CI, проверь воркфлоу на pull_request_target: код из форков не должен запускаться там, где есть доступ к токену публикации или к общему кэшу.
Что делать в Python-проекте
Те же приёмы атакующие используют и в PyPI: опечатки в именах пакетов, угнанные аккаунты, код, который выполняется при установке. Защищаться от них можно похожими способами.
- Закрепляй все версии, включая вложенные. Руками такой файл не пишут:
pip-compile --generate-hashes requirements.inиз pip-tools соберёт requirements.txt, где у каждой зависимости стоит точная версия и хеши sha256 её файлов. Новая версия пакета попадёт в проект, только когда ты сам пересоберёшь этот файл и посмотришь дифф. - Ставь зависимости в режиме проверки хешей.
pip install --require-hashes -r requirements.txtсверяет каждый скачанный файл с хешем из requirements.txt. Этот режим требует, чтобы все зависимости, включая вложенные, были закреплены через==и имели хеш, иначе pip остановит установку с ошибкой (документация pip). - Если работаешь с uv, команда
uv lockзаписывает в uv.lock точные версии всех зависимостей, и этот файл хранят в репозитории (документация uv о структуре проекта). На CI ставь окружение черезuv sync --locked: если lock-файл отстал от pyproject.toml, uv завершится с ошибкой и не станет его обновлять (раздел о lock-файле и синхронизации). - Проверяй пакеты, которые предложила нейросеть. Исследование про выдуманные имена пакетов проверяло и Python. Перед
pip installоткрой страницу пакета на pypi.org: есть ли ссылка на репозиторий, давно ли выходят релизы, совпадает ли имя с тем, что написано в документации библиотеки.
Что сделать сегодня
Если не хочется перестраивать весь процесс сразу, начни с трёх шагов. Проверь, что lock-файл лежит в репозитории и CI ставит зависимости через npm ci (в Python через uv sync --locked или pip install --require-hashes). Отключи автообновление зависимостей на проде. Пройдись по дереву зависимостей: часть пакетов, скорее всего, можно удалить.
Остальное можно настроить позже: provenance, --ignore-scripts, отдельные токены, проверку воркфлоу на pull_request_target. Три шага выше снижают риск в сценарии, общем для всех трёх атак 2026 года: свежая вредоносная версия попадает в проект при обычной установке. На них уйдёт один вечер.
