Как были скомпрометированы 95 пакетов Red Hat Cloud Services
Представьте: ваш CI/CD-пайплайн запускает обычную установку зависимостей. Ничего особенного — просто npm install перед сборкой. В этот момент, ещё до того как хоть одна строчка вашего кода успела выполниться, вредоносный загрузчик уже собрал SSH-ключи, токены GitHub Actions, учётные данные AWS, Azure, GCP, секреты Vault и Kubernetes-конфиг — и отправил всё это зашифрованным пакетом на удалённый сервер.
1 июня 2026 года команда Socket зафиксировала атаку на пространство имён @redhat-cloud-services в npm. По данным Socket, под удар попали 95 пакетов: chrome, frontend-components, insights-client, rbac-client, host-inventory-client, compliance-client, notifications-client и десятки других. Все заражённые версии были опубликованы в течение одного дня.
Атака классифицирована как «мини-Shai-Hulud»: та же механика install-time исполнения, сбора credentials, таргетирования CI/CD, шифрованной эксфильтрации и потенциального распространения по downstream-репозиториям. Socket не атрибутирует атаку конкретной группе — публичный релиз инструментария TeamPCP под названием Shai-Hulud снизил порог входа настолько, что похожие операции теперь доступны широкому кругу акторов.
КАК РАБОТАЕТ АТАКА
Механика элегантна и неприятна одновременно. Вредонос живёт в preinstall-хуке package.json: поле "preinstall": "node index.js" заставляет npm выполнить загрузчик автоматически во время установки — до того как пакет будет импортирован или вызван. Разработчик не успевает написать require('@redhat-cloud-services/chrome'), а код уже отработал.
Сам index.js — многоуровневый обфусцированный загрузчик. Первый слой: массив char-кодов с Caesar/ROT-подобным преобразованием и eval. После декодирования — асинхронный враппер, который использует Node crypto API для расшифровки двух AES-128-GCM-зашифрованных payload-блоков: один содержит Bun-хелпер, второй — основную нагрузку. Ключи вшиты в код в виде hardcoded hex-строк и скрыты несколькими слоями обфускации — они не видны при поверхностном анализе, хотя именно так исследователи Socket их и восстановили.
Расшифрованный payload записывается во временный файл с именем вида /tmp/p<random>.js, выполняется через Bun и немедленно удаляется. Если Bun не установлен — загрузчик скачивает его с GitHub (bun-v1.3.13) через curl, молча, в фоне. На рабочих станциях разработчиков payload дополнительно демонизируется: отвязывается от родительского процесса через child_process.spawn с флагом detached: true и продолжает работать после завершения установки. Lock-файл tmp.0987654321.lock предотвращает запуск дублирующих экземпляров.
Примечательная деталь: payload проверяет русскую локаль системы через Intl.DateTimeFormat().resolvedOptions().locale и переменные окружения LC_ALL, LC_MESSAGES, LANGUAGE, LANG. На системах с русской локалью поведение вредоноса изменяется — зачем именно сделана эта проверка, Socket не уточняет; возможные причины варьируются от фильтрации жертв до попытки обойти окружения анализа.
ЧТО СОБИРАЕТСЯ И КАК УХОДИТ
Авторы payload хорошо понимали, где живут деньги. Первым делом выполняется gh auth token — и GitHub CLI-токен уходит вместе с полным process.env. В CI-пайплайне переменные окружения — это не просто настройки сборки, это полный список того, что нужно атакующему: GITHUB_TOKEN, NPM_TOKEN, AWS_ACCESS_KEY_ID, VAULT_TOKEN, KUBECONFIG и ещё несколько десятков ключей, которые DevOps-команды годами аккуратно складывают в GitHub Secrets.
Параллельно файловый сборщик проходит по заранее известным путям. Он точно знает, где искать: ~/.aws/credentials, ~/.ssh/id_rsa, ~/.kube/config, ~/.docker/config.json, облачные токены Azure и GCP, .env-файлы прямо в рабочей директории проекта. Разработчики годами держат эти файлы на локальных машинах, не думая о том, что однажды npm install их прочитает. На всякий случай payload заглядывает и в ~/.bitcoin/wallet.dat — авторы явно не ограничивали себя в аппетитах.
Отдельный модуль заточен под GitHub Actions. Он проверяет GITHUB_ACTIONS=true, определяет Linux-раннер и пробует достать токены прямо из памяти процессов через sudo python3. Раннер GitHub Actions — идеальная цель: он одновременно держит в памяти токены деплоя, ключи облачных провайдеров и права на публикацию пакетов. Один заражённый npm install в пайплайне — атакующий потенциально получает всё, что доступно процессу: секреты из окружения, файлы credentials, токены из памяти раннера.
Эксфильтрация продумана с запасным каналом. Основной путь: данные сжимаются gzip, шифруются AES-256-GCM с эфемерным ключом, ключ оборачивается RSA-OAEP с публичным ключом атакующего — и уходят POST-запросом по HTTPS. Если основной канал недоступен, payload переключается на резервный: при наличии рабочего GitHub-токена он коммитит зашифрованные результаты в репозитории через Contents API. Commit message при этом содержит строку IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner:<token> — это одновременно уникальный IoC для threat hunting и прямая угроза не трогать скомпрометированный токен. Изящно и цинично.
И последнее — самораспространение. При наличии достаточных прав payload модифицирует .github/workflows/codeql.yml, добавляет .github/setup.js, записывает вредоносный index.js в репозитории жертвы. Следующий разработчик, который склонирует этот репозиторий и запустит npm install, станет новой точкой сбора. Атака спроектирована не как разовая кража, а как цепочка, которая расширяется сама.
ВРЕМЕННАЯ ШКАЛА
1 июня 2026 года заражённые версии 95 пакетов @redhat-cloud-services появились в npm. Судя по тому, что все версии вышли в течение одного дня, речь идёт о скоординированной публикации — не о постепенной инфильтрации. Исследователи Aikido считают наиболее вероятным вектором компрометацию механизма GitHub Actions OIDC trusted publishing, а не прямой взлом npm-аккаунта. Атакующие явно торопились: чем дольше пакеты висят в реестре незамеченными, тем больше систем успевают их установить.
В тот же день, 1 июня, команда Socket зафиксировала аномалию через поведенческий анализ. Это принципиально важный момент: атака была обнаружена не по сигнатуре известного вредоноса, не через CVE-базу и не по жалобам пострадавших — а потому что пакеты вели себя не так, как должны вести себя легитимные компоненты. Наличие preinstall-хука с node index.js, обфускация, шифрованные embedded-блоки, сетевые вызовы при установке — достаточно, чтобы поднять красный флаг автоматически.
2 июня 2026 года Socket опубликовала полный технический разбор с деобфусцированным кодом, IoC, хэшами и рекомендациями. Окно между публикацией заражённых пакетов и публичным раскрытием составило меньше суток — для supply chain атак это быстро, но даже за сутки системы, которые запускали npm install с заражёнными пакетами, уже успели пострадать.
Связь с Shai-Hulud Socket описывает как тактическое сходство, не как прямую атрибуцию. Совпадают ключевые TTP: install-time execution через preinstall, AES-GCM шифрование payload, двойной канал эксфильтрации через HTTPS и GitHub API, таргетирование CI/CD секретов, логика горизонтального распространения через модификацию репозиториев. После того как TeamPCP выложил инструментарий Shai-Hulud в открытый доступ в рамках конкурса на BreachForums, любая группа с минимальным техническим бэкграундом может воспроизвести похожую операцию. Это меняет threat landscape: порог входа для подобных операций значительно снизился.
ПОЧЕМУ ЭТО ВАЖНО
Классические IoC-модели заточены под детектирование известных вредоносных доменов, хэшей, сигнатур. Атаки на supply chain их обходят на архитектурном уровне: вредонос приходит из доверенного источника, публикуется через легитимный аккаунт вендора, устанавливается штатным инструментом. Пространство имён @redhat-cloud-services — не случайная цель. Это официальные компоненты Red Hat, которые по умолчанию считаются доверенными в любой security-политике, написанной без параноидального allowlisting.
Особенно разрушительно для CI/CD-сред. Сборочный раннер GitHub Actions — это концентратор секретов: он одновременно держит в памяти токены для деплоя, ключи облачных провайдеров, credentials реестров контейнеров, Vault-токены. Один npm install с заражённым пакетом в таком окружении — это потенциальный слив всего, что доступно процессу и нужно атакующему для дальнейшего продвижения. Не нужно эксплуатировать уязвимость в коде — достаточно оказаться в списке зависимостей.
Демонизация на рабочих станциях разработчиков добавляет ещё одно измерение: даже если CI защищён, разработчик, запустивший npm install локально, становится точкой входа. Фоновый процесс продолжает работать после завершения установки, сканирует файловую систему, читает ~/.ssh, ~/.aws, локальные .env-файлы. Удаление node_modules его не остановит.
ЧТО ДЕЛАТЬ ЕСЛИ УЖЕ УСТАНОВИЛИ
Первый шаг — определить экспозицию. Проверить package-lock.json, npm-shrinkwrap.json, yarn.lock, pnpm-lock.yaml на наличие пакетов @redhat-cloud-services версий от 1 июня 2026 года. Проверить исторические логи CI/CD за этот период — особенно сборки, которые запускали npm install.
Если заражённая версия была установлена — систему следует рассматривать как потенциально скомпрометированную и реагировать по сценарию инцидента: наличие версии в lockfile означает, что вредоносный код мог выполниться в момент установки. Удаление node_modules не помогает: вредонос отработал ещё во время установки, мог запустить фоновый процесс и изменить конфигурационные файлы. На рабочих станциях разработчиков — изолировать хост до полной проверки. Проверить следы выполнения:
# Артефакты payload во временных директориях
ls /tmp/p*.js 2>/dev/null
ls /tmp/b-* 2>/dev/null
ls /tmp/tmp.0987654321.lock 2>/dev/null
# Живые процессы вредоноса
ps aux | grep -E "bun|node index"
# Изменения в CI/CD конфигах
git log --all --oneline -- .github/workflows/ .github/setup.js
Ротация credentials обязательна для всех систем, где устанавливались заражённые пакеты. Перевыпустить: GitHub-токены (fine-grained PAT и classic PAT), npm publish-токены, ключи AWS (Access Key ID + Secret), service principal Azure, service account GCP, Vault-токены, Kubernetes service account токены, Docker registry credentials, SSH-ключи из ~/.ssh/, PyPI-токены. Для GitHub Actions — ротировать все секреты репозитория и организации, проверить OIDC-федерацию на предмет нелегитимных trust relationships.
После ротации — аудит активности. В GitHub: новые репозитории, неожиданные ветки, изменения в .github/workflows/, коммиты с файлами results-*.json или строкой IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner. В npm: неожиданные версии пакетов, публикации из автоматических токенов без соответствующих релизов в репозитории. CI/CD-артефакты и образы контейнеров, собранные в период экспозиции, пересобрать из чистого окружения.
КАК ЗАЩИТИТЬСЯ
Начать стоит с того, что эта атака не эксплуатирует уязвимость в коде. Она эксплуатирует доверие — доверие к официальному пространству имён, к привычному инструменту, к тому что npm install просто устанавливает пакет и не делает ничего лишнего. Поэтому и защита строится не вокруг патча, а вокруг архитектурных решений, которые ломают саму механику атаки.
Первое и самое прямое — отключить lifecycle scripts при установке зависимостей. Флаг --ignore-scripts запрещает выполнение preinstall, postinstall и других хуков. Этот механизм существует в npm для удобства — нативные модули могут компилироваться автоматически. Но то же удобство даёт атакующему возможность выполнить произвольный код на вашей машине в момент установки. Лучше явно разрешить конкретным пакетам запускать скрипты, чем молча доверять всему реестру:
# Установка без lifecycle scripts — ни один preinstall не выполнится
npm install --ignore-scripts
# Зафиксировать для всего проекта через .npmrc
echo "ignore-scripts=true" >> .npmrc
# yarn поддерживает тот же флаг
yarn install --ignore-scripts
Второе — npm ci вместо npm install в пайплайнах. Разница принципиальная: npm install может обновить версии пакетов в рамках semver-диапазона, npm ci устанавливает строго то, что зафиксировано в lockfile, и падает при любом расхождении. Сам lockfile — коммитить в репозиторий и относиться к нему как к доверенному артефакту, а не к автогенерируемому файлу, который можно пересоздать. Это не защищает от компрометации уже зафиксированной версии — как в этом случае — но исключает тихие обновления до заражённой версии в момент сборки.
Третье — сетевая изоляция CI-раннеров, и это, пожалуй, самый недооценённый контроль. Спросите себя: зачем процессу npm install доступ к 169.254.169.254? Или к secretmanager.googleapis.com? Или к api.github.com с нестандартными путями? Нет никакой причины. Egress-политика для раннеров должна разрешать только registry.npmjs.org и внутренний npm-прокси — всё остальное блокировать. Любой неожиданный исходящий запрос во время установки зависимостей — это алерт, который нужно расследовать немедленно.
Четвёртое — минимизировать секреты в сборочном окружении. Payload читает process.env целиком — и получает всё, что туда передано. Секреты для деплоя не нужны на шаге установки зависимостей. Разбить workflow на изолированные jobs, где каждый получает только то, что ему нужно. В GitHub Actions это решается явным указанием permissions на уровне job:
# Минимальные права по умолчанию на уровне workflow
permissions:
contents: read
# Только там, где действительно нужно писать
jobs:
deploy:
permissions:
contents: write
packages: write
Пятое — не доверять официальному namespace как гарантии. @redhat-cloud-services, @angular, @aws-sdk — за каждым стоит аккаунт или CI/CD-пайплайн публикации, который можно скомпрометировать. Socket обнаружил эту атаку не по сигнатуре, а по тому, что пакет вёл себя не так, как должен вести себя легитимный компонент: preinstall-хук, обфускация, шифрованные embedded-блоки, сетевые вызовы при установке. Интеграция такого инструмента в PR-проверки создаёт барьер ещё до того, как заражённая версия попадёт в lockfile и разойдётся по всем разработчикам команды.
ИНДИКАТОРЫ КОМПРОМЕТАЦИИ
Сетевые IoC из отчёта Socket:
# Основной канал эксфильтрации (задекодирован из payload)
https://api.anthropic[.]com:443/v1/api
# Детекшн-сигналы (легитимные сервисы, доступ в неожиданном контексте)
https://api.github[.]com
https://github[.]com/oven-sh/bun/releases/download/bun-v1.3.13/
https://registry.npmjs[.]org/-/npm/v1/tokens
# Облачные metadata-эндпоинты
http://169.254.169.254/latest/meta-data/iam/security-credentials/
https://secretmanager.googleapis[.]com
https://vault.azure[.]net
Хостовые IoC:
# Файлы и паттерны
/tmp/p*.js
/tmp/b-*/bun
/tmp/b-*/b.zip
tmp.0987654321.lock
# Строки в коде
f4abccab2
thebeautifulmarchoftime
IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner
Miasma: The Spreading Blight
createDecipheriv("aes-128-gcm"
"preinstall":"node index.js"
# SHA-256 заражённого index.js
21b6409a7b84446310daca5409ad6112ac60a1e4bef97736e53fff5f63bfdef4
Паттерны токенов для поиска в логах и файлах:
gh[op]_[A-Za-z0-9]{36,}
npm_[A-Za-z0-9]{36,}
ghs_[A-Za-z0-9]{36,}
ВЫВОДЫ
Атака на @redhat-cloud-services — ещё одно напоминание, что в 2026 году периметр защиты проходит не по границе вашей инфраструктуры, а по границе доверия к зависимостям. Официальное пространство имён крупного вендора не равно безопасному пространству имён. Компрометация upstream-аккаунта или CI/CD-пайплайна публикации — и, по данным Socket, 95 пакетов превращаются в оружие одновременно.
Если вы используете пакеты @redhat-cloud-services в проектах или CI/CD — проверьте lockfile прямо сейчас на наличие версий от 1 июня 2026 года. Если нашли — рассматривайте систему как потенциально скомпрометированную и начинайте ротацию credentials. Если не нашли — это хороший момент выстроить процесс, который поймает следующую аналогичную атаку раньше, чем она успеет отработать.
Полный список затронутых пакетов и версий Socket ведёт на странице кампании: socket.dev/supply-chain-attacks/red-hat-cloud-services-package-compromise
