npm-малварь теперь срабатывает при import
14 июля 2026 года пять пакетов экосистемы AsyncAPI были переопубликованы с вредоносным кодом внутри. Тремя днями раньше через похожее прошёл пакет jscrambler. Ни одной из кампаний не понадобился хук postinstall: обе срабатывали в момент импорта модуля.
Эта деталь и есть вся история — и именно она обесценивает меру защиты, которую многие пайплайны сегодня считают достаточной.
Предположение, которое только что рухнуло
Годами модель угроз npm выглядела удобно и просто: вредоносный пакет выполняет полезную нагрузку во время установки, через lifecycle-хуки preinstall и postinstall. Защита строилась ровно под эту форму. Сканеры помечали хуки. Пайплайны CI добавляли --ignore-scripts. А npm 12 сделал install-скрипты явно включаемыми — это было настоящее улучшение.
Наблюдения Unit 42 за кампаниями в npm в первой половине 2026 года читаются как каталог одного и того же паттерна: Bitwarden (22 апреля), SAP/CAP (29 апреля), TanStack (11 мая), @antv (19 мая), Red Hat (1 июня) — preinstall-хук за preinstall-хуком.
Потом дверь закрыли, и нагрузка переехала туда, куда эта дверь не достаёт.
Что изменилось
В компрометации AsyncAPI затронутые релизы — @asyncapi/specs 6.11.2 и 6.11.2-alpha.1, @asyncapi/generator 3.3.1, @asyncapi/generator-components 0.7.1, @asyncapi/generator-helpers 1.1.1, опубликованные примерно за 90 минут — вообще не объявляли lifecycle-хуков. Вывод Microsoft сформулирован прямо: как только любая сборка или приложение импортирует отравленный пакет, внедрённый блок выполняется немедленно, и привычная мера npm install --ignore-scripts его не нейтрализует.
Инцидент с jscrambler показывает ту же эволюцию, сжатую в один день. Версии с 8.14.0 по 8.17.0 использовали хук preinstall, указывающий на dist/setup.js. Начиная с 8.18.0 дроппер был внедрён прямо в dist/index.js и точку входа CLI: он самовыполняется при импорте, переживает --ignore-scripts и невидим для сканеров, читающих только хуки в package.json.
Как выглядит дроппер, срабатывающий при импорте
Ничего экзотического. Тело модуля выполняется при require/import, поэтому достаточно одного побочного эффекта на верхнем уровне:
// dist/index.js — настоящие экспорты ниже, дроппер выше
const { spawn } = require('node:child_process')
spawn('node', [payloadPath], {
detached: true, // переживает родительский процесс
stdio: 'ignore', // ничего в выводе сборки
windowsHide: true, // без окна консоли
}).unref() // родитель завершается нормально
module.exports = require('./real-entry.js')Родительский процесс завершается чисто, лог сборки остаётся зелёным. В случае AsyncAPI первая стадия затем подтягивала зашифрованный бандл около 8,2 МБ из IPFS по зашитому CID, расшифровывала его встроенными ключами HKDF-SHA256 / AES-256-GCM и запускала модульный runtime командного управления с собственным механизмом закрепления.
Раз --ignore-scripts больше не определяет исход, точка влияния смещается выше — к тому, какие версии вообще попадают на машину:
{
"overrides": { "@asyncapi/specs": "6.11.1" },
"scripts": {
"ci": "npm ci --ignore-scripts && npm audit signatures"
}
}Оговорки, которые стоит уважать
Lockfile необходим, но недостаточен. Он фиксирует версию, но скомпрометированная версия, попавшая в lockfile до обнаружения, фиксируется столь же добросовестно. Сочетайте фиксацию с задержкой по возрасту публикации, чтобы сборка не тянула релизы, вышедшие часы назад.
Чистите кеши. Рекомендации Microsoft прямо включают очистку кешей npm и Yarn: отравленный тарбол в кеше живёт дольше, чем удаление версии из реестра.
Ротируйте учётные данные на сборочных машинах, а не только на ноутбуках. Обе нагрузки собирали облачные учётные данные, токены и конфигурации AI-инструментов с ключами API. У CI-раннеров этот набор самый ценный.
Следите за исходящим трафиком, а не только за пакетами. Блокировка IPFS-шлюзов на периметре ломает вторую стадию, даже если первая уже отработала.
Итог
--ignore-scripts стоит оставить — он закрывает реальную дверь. Но он перестал быть границей. Всё, что зависимость может сделать во время импорта, она теперь делает не спрашивая, а значит работающие рычаги — это фиксация версий, задержка по возрасту публикации, гигиена кеша и ограничение исходящего трафика сборочной инфраструктуры.
Первоисточники: Microsoft Security Blog — компрометация AsyncAPI в npm и Socket — атака на цепочку поставок jscrambler.