Взлом AntV в npm: доверенный пакет украл доверие

Атака на аккаунт мейнтейнера AntV показала: знакомое имя, semver и подписи не спасают, если вредоносный код запускается при установке.

2026-05-26 GIGATAP Team #security
#npm#supply-chain-security#open-source

Что произошло#

19 мая 2026 года через скомпрометированный аккаунт мейнтейнера npm были опубликованы вредоносные версии для большой части экосистемы AntV и связанных пакетов, пишет Chainguard. Окно атаки было коротким: автоматические волны публикаций пришлись примерно на 01:39–02:18 UTC, а самый плотный всплеск — на 01:56–02:06 UTC.

Затронутый аккаунт поддерживал 547 npm-пакетов. По данным Chainguard, атакующий выпустил вредоносные версии для 314 из них — всего 637 вредоносных версий пакетов. В список попали пакеты Alibaba AntV для визуализации данных и популярные самостоятельные пакеты, в том числе с сотнями тысяч и более загрузок в неделю.

Исследователи связывают инцидент с более широкой кампанией Mini Shai-Hulud, которую также ассоциируют с более ранними компрометациями библиотек SAP Cloud Application Programming Model, TanStack и intercom-client. Chainguard называет этот эпизод Shai-Hulud Wave 5 — частью кампании, активной с сентября 2025 года.

Это не мелкая неприятность в npm. По оценке Chainguard, вся кампания Mini Shai-Hulud уже охватывает более 1 000 вредоносных версий в более чем 500 уникальных пакетах в npm, PyPI и Composer. Пакеты, затронутые в событии вокруг AntV, суммарно давали около 16 млн загрузок в неделю.

Проблема была не в dist-tag#

Важная деталь: атакующий, как сообщается, в большинстве пакетов не менял dist-tag latest. На первый взгляд это успокаивает, но типичные установки от этого не защищены.

Разрешение версий по npm semver всё равно может выбрать самую высокую версию, подходящую под заданный диапазон. Зависимость вроде "echarts-for-react": "^3.0.6" при следующей чистой установке может подтянуть вредоносную версию, если она попадает в диапазон. Lockfile снижает риск только если он есть, соблюдается и не был пересоздан в опасное окно.

Из-за этого такие компрометации особенно опасны для CI. Разработчик может не увидеть явной смены имени пакета. Репозиторий по-прежнему зависит от той же доверенной библиотеки. Аккаунт мейнтейнера настоящий. Namespace пакета настоящий. Вредоносная версия лежит внутри обычной ветки версий.

Причина сбоя — не наивность пользователя. Это нормальное поведение пакетного менеджера, столкнувшееся со скомпрометированной личностью публикации.

Что делал payload#

По данным Chainguard, каждая вредоносная версия содержала npm lifecycle hook, который выполнялся при запуске npm install. Hook запускал обфусцированный Bun-скрипт размером примерно 498KB. Если Bun не был установлен, malware пытался тихо установить его и продолжить выполнение.

Payload был заточен под кражу учетных данных и распространение. Chainguard описывает логику сбора более чем 20 типов секретов: AWS-учетные данные и metadata sources, Google Cloud, Microsoft Azure, GitHub personal access tokens, npm tokens, SSH-ключи, Kubernetes service account tokens, HashiCorp Vault tokens, Stripe keys, строки подключения к базам данных, а также локальные хранилища менеджеров паролей — 1Password, Bitwarden, pass и gopass.

Malware также обращался к cloud metadata endpoints и пытался выполнить Docker container escape через host socket. Это важно: многие установки идут внутри CI runners или developer containers, у которых всё еще есть доступ к чувствительным build, deploy или cloud credentials.

Эксфильтрацию сделали трудной для чтения постфактум. Chainguard пишет, что украденные данные сериализовались, сжимались, шифровались RSA и отправлялись на инфраструктуру атакующих через пути запросов в стиле OpenTelemetry, чтобы слив выглядел как обычный трафик. Если описание точное, сетевые логи могут показать факт выхода данных из среды, но не их содержимое.

Был и резервный канал. Если command-and-control exfiltration не срабатывала, malware мог использовать украденный GitHub token, создать публичный репозиторий в аккаунте жертвы и закоммитить собранные данные в JSON. Chainguard отмечает репозитории с перевернутым описанием niagA oG eW ereH :duluH-iahS, то есть “Shai-Hulud: Here We Go Again.”

Резервный путь грубый, но рабочий: GitHub-аккаунт самой жертвы превращается в dead drop.

Почему одних подписей мало#

По данным Chainguard, часть скомпрометированных пакетов также подделывала SLSA provenance attestations через Sigstore, когда запускалась в CI-среде с доступным OIDC token.

Это самая неприятная часть инцидента. Проверка подписи отвечает на узкий вопрос: был ли артефакт подписан через ожидаемый механизм? Сама по себе она не доказывает, что релиз был намеренным или что путь кода до подписи был чистым.

Если атакующий контролирует путь публикации и может выполняться внутри доверенного CI-контекста, итоговый артефакт может получить правдоподобную provenance. Это не делает Sigstore бесполезным. Это значит, что provenance нужно связывать с policy, review, ожиданиями по source, изоляцией build и anomaly detection. Валидная подпись — это свидетельство. Но не полноценная модель доверия.

То же касается цепочек зависимостей. Chainguard сообщает, что многие вредоносные версии добавляли entry на orphan commit в легитимном GitHub-репозитории, доставляя вторую копию payload через маршрут, который выглядел доверенным. Сканеры, которые проверяют только отдельные поля пакета или опубликованные tarballs, могут пропустить такой ход.

Главный урок простой: supply-chain controls ломаются, когда знакомые имена принимают за доказательство безопасности.

Что проверить сейчас#

Рекомендация Chainguard жесткая: если среда устанавливала любой пакет, поддерживаемый скомпрометированным аккаунтом, и resolved version была опубликована между 01:39 и 02:18 UTC 19 мая 2026 года, считайте среду полностью скомпрометированной.

Это правильное смещение в сторону осторожности. Payload охотился за credentials, cloud metadata, CI secrets, GitHub tokens, npm tokens, Kubernetes-материалами и локальными секретами разработчиков. Если install context мог это прочитать, считайте, что malware мог это забрать.

Немедленные проверки:

  • Проверьте lockfiles и dependency manifests на пакеты из затронутого набора, который поддерживался atool. Chainguard отмечает, что полный список пакетов есть в публичных advisories.
  • Найдите установки или CI runs, которые resolved версии пакетов, опубликованные в окно атаки 19 мая.
  • Ротируйте credentials, доступные с затронутых машин разработчиков и CI runners: cloud keys, GitHub tokens, npm tokens, SSH-ключи, kubeconfig-материалы, Vault tokens, Stripe keys, database credentials и другие deploy secrets.
  • Проверьте GitHub accounts и organizations на неожиданные публичные репозитории, особенно с перевернутым описанием Shai-Hulud. Перед удалением сохраните содержимое для forensics.
  • Просмотрите сетевые логи на неожиданные обращения к cloud metadata endpoints из non-cloud contexts и подозрительный egress в стиле OpenTelemetry из install environments.
  • Проверьте активность GitHub: неожиданное создание репозиториев, необычное использование tokens и Sigstore signing operations, которые не соответствуют реальным релизам.

Для будущих установок npm install --ignore-scripts может снизить риск выполнения lifecycle hooks, но это не универсальное решение. Некоторые легитимные пакеты зависят от install scripts. Такой контроль полезен как часть осознанной build policy, а не как команда, которую вставляют в панике.

Практический вывод#

Этот инцидент хорошо показывает, почему доверие к зависимостям должно учитывать runtime. Имя пакета было знакомым. Аккаунт мейнтейнера был настоящим. В некоторых случаях сигналы подписи могли выглядеть валидными. Сбой произошел там, где выполнение кода при установке дало новой версии пакета мгновенный доступ к секретам.

Лучшие защиты скучные и архитектурные: pinning, дисциплина lockfile, ограниченные CI credentials, блокировка install scripts там, где это возможно, short-lived tokens, egress controls, secret scanning и package ingestion policies, которые добавляют задержку или review перед попаданием новых версий в production builds.

Chainguard пишет, что его клиенты были защищены, потому что repository cooldown policy заблокировала вредоносные версии в окно атаки, а сборки Chainguard идут from source без install-time scripts. В этом и общий смысл: самая безопасная система не та, которая мгновенно угадывает каждый вредоносный пакет. А та, которая ограничивает, что новая версия пакета может сделать до того, как кто-то успел ее проверить.