11 мая 2026 года группа TeamPCP, известная по предыдущим инцидентам с SAP, Checkmarx и Bitwarden, одновременно атаковала два крупнейших реестра пакетов - npm и PyPi. Злоумышленники внедрили вредоносный код в пакеты из нескольких пространств имён, включая @tanstack (с популярнейшей библиотекой маршрутизации react-router, около 12 миллионов загрузок в неделю), @uipath, @mistralai и guardrails-ai. Атака использовала связку из трёх уязвимостей в GitHub Actions, что позволило опубликовать скомпрометированные версии без кражи учётных данных npm - вместо этого были перехвачены токены OIDC (протокол аутентификации, позволяющий рабочему процессу GitHub Actions получать временные токены доступа к облачным ресурсам).
Описание
Механизм атаки оказался изощрённым. Сначала злоумышленник создал форк репозитория TanStack/router, переименовав его в zblgg/configuration, чтобы скрыться из списка форков. Затем он открыл pull request, который запускал workflow с триггером pull_request_target. Этот workflow проверял и выполнял код из форка, отравляя кэш GitHub Actions вредоносным магазином pnpm. Когда легитимные сопровождающие объединяли свои PR в основную ветку, workflow сборки восстанавливал отравленный кэш. Двоичные файлы под управлением атакующего извлекали токены OIDC напрямую из памяти процесса GitHub Actions ("/proc/<pid>/mem"). С этими токенами злоумышленник публиковал вредоносные пакеты, не имея доступа к настоящим npm-учётным данным.
Опубликованные пакеты содержали два вектора заражения: запись optionalDependencies, указывающую на orphan-коммит (github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c), который выполнял полезную нагрузку через скрипт prepare, и скрытый файл размером около 2,3 МБ router_init.js, добавленный прямо в архив пакета. Сразу после атаки на @tanstack были скомпрометированы пакеты в пространстве @uipath. Они использовали preinstall-скрипт (node setup.mjs), загружавший среду выполнения Bun и запускавший полезную нагрузку. Этот же механизм ранее применялся при компрометации SAP. Вариант для UiPath содержал повторно обфусцированную версию той же нагрузки, но с другим ключом кампании и идентичной командной инфраструктурой.
Полезная нагрузка представляла собой кражу учётных данных и самовоспроизводящегося червя. Она нацеливалась на токены CI/CD (GitHub Actions OIDC, GitLab, CircleCI), облачные учётные данные (AWS IMDSv2, GCP, Azure), сервисные аккаунты Kubernetes, HashiCorp Vault и токены реестров пакетов. Используя украденные npm-токены и токены OIDC, червь публиковал отравленные версии других пакетов, к которым жертва имела доступ на запись. Таким образом он распространялся по всей экосистеме npm.
Для эксфильтрации данных применялись три избыточных канала: домен-тайпсквот git-tanstack[.]com, децентрализованная сеть Session messenger (устойчивая к блокировкам, поскольку использует криптографическое шифрование и пиринговую архитектуру) и GitHub API "мёртвые капли" - репозитории с описанием "Shai-Hulud: Here We Go Again", созданные с помощью украденных токенов. Канал Session messenger стал новым элементом: он децентрализован и значительно сложнее для подавления, чем обычные домены или GitHub.
На машинах разработчиков вредонос устанавливал постоянный демон gh-token-monitor (через LaunchAgent на macOS или systemd на Linux), опрашивающий GitHub каждые 60 секунд. Если из-за отзыва токена возвращалась ошибка 40X, демон пытался выполнить "rm -rf ~" - удаление домашней директории. Впрочем, демон автоматически завершал работу через 24 часа, так что деструктивный триггер мог не сработать. Как и в предыдущих вариантах Mini Shai-Hulud, вредонос проверял настройки локали системы и завершал работу без эксфильтрации, если находил русский язык.
Эксперты компании Wiz в отчёте связали атаку с группой TeamPCP, отметив эволюцию методов. Отдельного внимания заслуживает Python-вариант, атаковавший пакеты guardrails-ai и mistralai на PyPi. Он отличается от JavaScript-версий: заражённый пакет содержал всего 13 строк кода, которые загружали и выполняли файл git-tanstack[.]com/tmp/transformers.pyz. В отличие от других нагрузок TeamPCP, этот код не был обфусцирован - он представлял собой модульный стилер учётных данных. Стилер работал только на Linux, избегал систем с русской локалью или менее чем четырьмя процессорами. Он собирал данные из хранилищ паролей (1Password, Bitwarden) и отправлял их на сервер 83.142.209[.]194, а в случае неудачи записывал в репозиторий GitHub с описанием "PUSH UR T3MPRR".
Особенно тревожным стал деструктивный функционал Python-версии. Если система оказывалась в часовом поясе Израиля или Ирана (определяемом по локали и языку), вредонос с вероятностью 1/6 запускал воспроизведение mp3-файла на полную громкость и пытался выполнить "rm -rf", удаляя файлы. Современные дистрибутивы Linux без флага "--no-preserve-root" не дают этой команде сработать, но обновлённая версия от 13 мая устраняла это ограничение.
Для экосистемы разработки эта атака стала серьёзным сигналом. Уязвимости в GitHub Actions, особенно в триггерах pull_request_target, позволяют злоумышленникам получать доступ к высокопривилегированным токенам, не взламывая учётные записи. Использование Session messenger для эксфильтрации делает блокировку каналов почти невозможной. А самовоспроизводящийся червь, публикующий вредоносные пакеты через украденные токены, способен поразить тысячи проектов за короткое время. Команда npm уже удалила скомпрометированные версии, но инцидент показал: защита цепочки поставок требует не только проверки кода, но и строгих ограничений для токенов CI/CD, использования минимальных привилегий и постоянного мониторинга нештатных действий в реестрах пакетов.
Индикаторы компрометации
IPv4
- 83.142.209.194
URLs
- git-tanstack.com/tmp/transformers.pyz
SHA1
- 12f35b1081b17d21815b35feb57ab03d02482116
- 820fa07a7328b6cf2b417078e103721d4d8f2e79
- e7d582b98ca80690883175470e96f703ef6dc497
SHA256
- 1e8538c6e0563d50da0f2e097e979ebd5294ce1defe01d0b9fe361ba3bed1898
- 2258284d65f63829bd67eaba01ef6f1ada2f593f9bbe41678b2df360bd90d3df
- 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
- ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c