Более 1300 пакетов в npm с суммарными 2 млрд загрузок в месяц оказались заражены червем ChainDrop. Эта атака на npm уже задела популярные зависимости вроде Keyv, Cacheable, flat-cache и file-entry-cache, а значит речь идет не о маргинальном модуле из забытого репозитория, а о кусках экосистемы, которые легко доезжают до продакшена через обычный lockfile. Для команд, собирающих Node.js-проекты через CI/CD, новость неприятна по простой причине: вредоносные релизы выглядели вполне легитимно.
По данным BleepingComputer, входной точкой стал захват GitHub-аккаунта мейнтейнера Keyv. Дальше злоумышленник пошел не в лоб через npm, а через привычный путь open source-поддержки: вредоносные файлы попадали в основную ветку проекта, после чего выпускалась новая версия пакета. Aikido насчитала как минимум 868 зараженных пакетов и 1381 вредоносную версию, но общий счет уже перевалил за 1300 пакетов и продолжает расти. В списке затронутых оказались не только утилиты одного мейнтейнера, но и пакеты, связанные с Deliveroo, Ornikar, OneReach, Picsart, Qlik и ServiceTitan.
Самый показательный момент здесь не масштаб, а то, как именно были собраны релизы. Пакеты публиковались через их штатные GitHub Actions-воркфлоу, поэтому в npm они появлялись с валидной provenance-информацией. Для многих команд это почти инстинктивный сигнал доверия: если релиз пришел из знакомого репозитория, собран в официальном пайплайне и снабжен нормальной метаданной цепочкой, значит можно выдыхать. История с ChainDrop как раз ломает эту иллюзию. Когда атакующий получает доступ к учетной записи мейнтейнера, он не подделывает процесс сборки, а аккуратно использует его по назначению, только в чужих интересах.
Внутри зараженных версий исследователи нашли как минимум два компонента: setup.mjs и обфусцированный вредоносный скрипт Math_Symbol.js, который в части пакетов также встречался под именем math_init.js. В package.json появлялся preinstall-хук, запускавший setup.mjs еще до завершения npm install. Дроппер подтягивал Bun из официального GitHub-релиза, через него исполнял основную нагрузку и затем удалял временный каталог. Ход почти инженерски красивый, если забыть, зачем он нужен: не тащить с собой громоздкий бинарник, а использовать доверенный рантайм, который у многих не вызовет лишних вопросов ни на рабочей станции разработчика, ни на CI runner.
Дальше начинался уже не трюк с пакетом, а полноценный сбор доступа. Скрипт выгружал окружение процесса, локальные конфиги и credential-файлы, GitHub PAT и workflow-токены, npm-токены, секреты GitHub Actions, в том числе на self-hosted runner, а также учетные данные AWS, параметры SSM и Secrets Manager, Kubernetes secrets, токены HashiCorp Vault, ключи Azure, GCP, Stripe, Slack, Twilio и данные для баз. Отдельно неприятен штрих с проверкой токенов в реальном времени через registry.npmjs.org/-/whoami: малварь сначала убеждалась, что ключ живой, и только потом уносила его дальше. По данным Wiz, индикатором компрометации также может быть домен npm-cache[.]com.
На этом ChainDrop не останавливался. Исследователи описывают его как самораспространяющийся червь: если на зараженной машине или раннере уже были токены, открывающие доступ к другим репозиториям и пакетам, вредоносный код пытался пройти по этой цепочке дальше. Для JavaScript-экосистемы это особенно болезненный сценарий, потому что удар приходится по зависимостям из глубины дерева, которые редко кто отслеживает вручную. Разработчик может вообще не знать, что его сервис тянет через несколько уровней тот же flat-cache, а команда платформы может заметить проблему уже после публикации нового релиза или странного коммита в связанном проекте. Именно поэтому эта атака на npm выглядит не как единичный эксцесс, а как зрелая supply-chain операция.
Практический вывод для бизнеса и разработки жесткий. Если затронутая версия была установлена, простое удаление пакета почти ничего не меняет: администраторы должны считать рабочую станцию разработчика или CI/CD runner скомпрометированным, пересобирать его из безопасного источника или из чистой резервной копии, перевыпускать все токены и секреты, доступные на хосте, и поднимать логи на предмет несанкционированного доступа, подозрительных публикаций и неожиданных коммитов. Дополнительно стоит сверить версии зависимостей и артефакты со списками, которые публикуют Wiz, StepSecurity, Aikido, Socket и Ox Security. Неприятная мораль в том, что supply-chain атака на npm теперь легко проходит проверку на внешнюю аккуратность: подписи, provenance и знакомый CI больше не дают прежнего чувства контроля.
История с ChainDrop бьет не только по npm, но и по комфортной идее, что безопасность цепочки поставки можно закрыть одной технологией и несколькими галочками в настройках репозитория. Пока пакеты подписываются честным пайплайном, а пайплайн живет внутри уже захваченной учетной записи, доверенная цепочка продолжает работать против владельца. Следующий вопрос для рынка звучит неприятно, но очень по делу: как строить защиту так, чтобы компрометация мейнтейнера, секретов CI и прав на публикацию не превращала любую популярную зависимость в транспорт для червя. После такой атаки на npm ответ явно будет лежать не в новых бейджах verified, а в более жестком обращении с токенами, правами и средами сборки.