Shai-Hulud вернулся: червь заразил keyv, cacheable и сотни npm-пакетов

4 августа 2026 года мейнтейнер библиотеки keyv проснулся не тем человеком, каким лёг спать: его GitHub-аккаунт уже несколько часов публиковал заражённые версии пакетов, которые скачивают два миллиарда раз в месяц. Это не «утечка токена у ноунейма» — это keyv, cache-manager, flat-cache, file-entry-cache. Они лежат в зависимостях у половины Node-проектов планеты, чаще всего транзитивно: вы про них не знаете, но npm install их тянет.

Разберём этот инцидент не как страшилку, а по минутам — потому что сам механизм важнее заголовка. Червь Shai-Hulud (отсылка к песчаным червям из «Дюны») устроен так, что заражённая машина сама становится распространителем. И если вы держите self-hosted стек, CI-раннер или просто билдите фронтенд на своём сервере — вектор атаки проходит ровно через вашу сборку.

Хронология: как разворачивался день

Note:

Все временные метки — по данным Wiz и Aikido Security; расследование на момент публикации ещё шло, цифры уточнялись в течение суток.

~09:00 UTC. Скомпрометирован GitHub-аккаунт мейнтейнера экосистемы keyv/cacheable. В основную ветку репозиториев уходят вредоносные файлы, тут же выкатывается релиз. Ключевая деталь: заражённые версии опубликованы в npm с валидным provenance от GitHub Actions. То есть значок «собрано доверенным пайплайном», на который многие привыкли полагаться, здесь ничего не гарантировал — злоумышленник действовал изнутри легитимного процесса.

Первая волна — конкретные версии. По данным Aikido, в неё попали, в частности:

keyv               6.0.0     (~604 млн загрузок/мес)
flat-cache         6.1.24    (~580 млн)
file-entry-cache   11.1.6    (~571 млн)
cacheable-request  13.0.20   (~137 млн)
cache-manager      7.2.10    (~16 млн)
cacheable          2.5.1
@cacheable/utils   2.5.1
ecto               5.0.1

~13:37 CEST. Масштаб уже другой: заражено как минимум 434 пакета в 1381 версии, суммарно свыше 2 млрд установок в месяц. Червь размножился сам — не руками атакующего.

Warning:

Если между 9 утра и вечером 4 августа у вас проходил npm install или npm ci без залоченных версий (или с обновлением lock-файла) — считайте, что вы могли затянуть заражённую версию. Проверять нужно не только прямые зависимости, но и весь package-lock.json.

Анатомия заражения: что делает пейлоад

Механика двухступенчатая и стоит того, чтобы её понять — на ней держится весь эффект «эпидемии».

Шаг 1. Хук на установке. В каждый заражённый пакет добавлены setup.mjs и Math_Symbol.js, а в package.json прописан "preinstall": "node setup.mjs". Хук срабатывает автоматически при установке — никакого запуска приложения не требуется.

Шаг 2. Дроппер тянет Bun. setup.mjs скачивает рантайм Bun и запускает основной пейлоад мимо привычного Node — файл Math_Symbol.js размером ~728 КБ с обфусцированным кодом кражи секретов. В сетевых логах это видно по User-Agent Bun/1.3.13 и временным каталогам /tmp/bun-dl-*/.

Шаг 3. Тотальный сбор секретов. Сканер проходит по файловой системе ~200 glob-шаблонами (в 64 параллельных потока) и выгребает буквально всё, до чего дотянется:

Error:

Что уходит атакующему при удачном заражении:

  • npm-токены из ~/.npmrc (с проверкой валидности прямо в реестре);
  • GitHub-токены: classic PAT, OAuth, GitHub App; на CI-раннерах — OIDC-токены для публикации;
  • AWS: ключи из конфигов, переменных окружения, EC2 metadata, ECS-эндпоинтов и Secrets Manager;
  • секреты Kubernetes (service account tokens, секреты через API) и HashiCorp Vault;
  • токены Stripe и Slack;
  • по данным Wiz — ещё и хранилища доступов ИИ-инструментов (Claude, OpenAI, Cursor, Gemini) и криптокошельки (Solana, Monero, Foundry);
  • SSH-ключи, .env, Terraform state, docker-конфиги, настройки IDE.

Шаг 4. Размножение. Дальше червь заражает всё, до чего дотянулся украденными правами:

  • через npm: украденным токеном находит доступные аккаунту пакеты, бампает патч-версию, вставляет тот же хук и публикует заново. Второе поколение узнаётся по имени math_init.js вместо Math_Symbol.js;
  • через GitHub: server-to-server токеном GitHub App коммитит в до 50 веток репозитория, прописывая вредоносные хуки в .claude/settings.json и .vscode/tasks.json — чтобы код выполнился при следующем открытии IDE. Коммиты подписаны автором «claude».

Шаг 5. Вывод данных. Собранные секреты шифруются RSA (расшифровать может только владелец приватного ключа) и заливаются в публичные GitHub-репозитории с описанием «Shai-Hulud: Here We Go Again» — таких насчитали около 1300. Если залить не удалось — запасной канал npm-cache[.]com:443/router, а адреса C2 подтягиваются из смарт-контракта в сети Ethereum. Это заметный момент: захардкоженный домен можно заблокировать, а адрес в блокчейне не отзовёшь и не заремувишь по abuse-жалобе.

Одна деталь, которая спасает: preinstall в npm 12

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

Info:

По данным JFrog, начиная с npm 12 хуки preinstall по умолчанию не выполняются. Если ваши сборки уже на npm 12+, автоматического заражения именно через этот вектор не происходит — пейлоад просто не стартует при установке. Это не повод расслабляться (файлы всё равно окажутся в node_modules, и запуск скрипта вручную их активирует), но огромная разница между «заражён при первом же npm ci» и «нужно ещё что-то запустить».

Отсюда первый практический вывод, который можно сделать сегодня: проверьте версию npm на всех билд-агентах и dev-машинах и подтяните её. Плюс — заведите привычку ставить зависимости с --ignore-scripts там, где это возможно (многие пакеты живут без postinstall-хуков нормально).

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

Порядок действий, если вы или ваша команда касаетесь Node-экосистемы:

Success:
  1. Найдите заражённые версии. Прогоните по package-lock.json список известных пакетов и версий (полные списки ведут Wiz и Aikido). Смотрите не только прямые зависимости.
  2. Поищите артефакты на диске: файлы setup.mjs, Math_Symbol.js, math_init.js; каталоги /tmp/bun-dl-*; неожиданный "preinstall" в package.json зависимостей.
  3. Проверьте git-историю и ветки на коммиты от «claude» и правки .vscode/tasks.json / .claude/settings.json.
  4. Ротируйте всё, что могло утечь: npm-токены, GitHub PAT/OAuth, AWS-ключи, kube-токены, секреты Vault, ключи Stripe/Slack, SSH-ключи. Не «на всякий случай подозрительные», а всё, что было доступно с заражённой машины.
  5. Пересоберите потенциально скомпрометированные раннеры с нуля, а не «почистите».
  6. На будущее — залочьте зависимости, включите allowlisting пакетов и проверку целостности.
Security:

Отдельно про CI: именно раннеры — самая ценная добыча. На них лежат самые «жирные» секреты (ключи для публикации, доступ к облаку), а npm ci там выполняется автоматически и часто с широкими правами. Если у вас есть хоть малейшее подозрение, что заражённая версия проехала через пайплайн, — начинайте ротацию именно с CI-секретов, до всего остального.

Что во всём этом важно, а что — шум

Трезво: паниковать из-за самого факта «в npm снова малварь» бессмысленно — это происходит регулярно. Важно другое. Во-первых, атака показала, что provenance от GitHub Actions не равен безопасности, если скомпрометирован сам мейнтейнер, — на этот значок нельзя полагаться как на финальную гарантию. Во-вторых, связка «самораспространение + кража CI-секретов + C2 в блокчейне» — это уже не разовый вброс, а устойчивый шаблон, который будет повторяться. В-третьих, спасение оказалось до обидного простым: свежий npm и --ignore-scripts отсекают основной вектор.

Чего пока делать не стоит — верить в конкретные итоговые цифры «пострадавших» как в окончательные. На момент публикации расследование шло, и оценки (200+, 434+, 868 пакетов у разных исследователей) расходятся, потому что считают по-разному и в разные часы. Факт масштаба подтверждён независимо несколькими командами; точное число ещё уточняется.

Источники

Связанное на форуме: Linux закрыл staging для патчей от нейросетей.

Question:

Как у вас устроена защита от подобного на билд-агентах: --ignore-scripts по умолчанию, приватный proxy-реестр, allowlist пакетов, что-то ещё? И проверяли ли уже свои lock-файлы по этому инциденту?