Supply chain атаки на npm и PyPI осенью 2025: наш ответ через SBOM, зеркала и Sigstore
После серии атак на npm и PyPI ужесточили политики в CI/CD: рассказываем про SBOM, зеркалирование зависимостей и проверку артефактов через Sigstore.
Новые атаки на supply chain через публичные пакетные репозитории - осень 2025
Осенью этого года supply chain снова напомнил о себе. В октябре прилетело несколько волн: вредоносные пакеты в npm с именами, почти неотличимыми от популярных библиотек, и компрометация сопровождающего крупного PyPI-пакета с последующей публикацией заражённой версии. Подробности разбирали в публичных postmortem'ах, нас они задели косвенно - один из клиентов тянул зависимость из PyPI в prod-pipeline без какой-либо верификации. Инцидента не случилось, но разговор о том, как вообще у них устроен supply chain, стал неотложным.
Что увидели при аудите
Картина типичная, без каких-то особенных провалов - просто дефолтные настройки, которые никто не трогал, пока «и так работает».
- pip install из публичного PyPI прямо в CI без фиксации хешей и без проверки подписей.
requirements.txtс незакреплёнными мажорными версиями в нескольких местах. - npm ci из публичного реестра, lock-файл есть, но integrity-поля не проверяются дополнительно. Кастомных политик на уровне registry - ноль.
- Docker-образы тянутся по тегу, не по дайджесту.
latestвстречается в нескольких Dockerfile'ах. - SBOM отсутствует как класс. Никто не знает, что именно вошло в последний prod-деплой, не считая lock-файлов.
Всё это - не халтура команды. Это просто стандартный 2022 год, оставшийся нетронутым в 2025-м.
Что и зачем меняли
Работали поэтапно, не пытаясь переписать всё разом.
SBOM как базовый артефакт сборки
Первый шаг - начать хотя бы знать, что внутри. Внедрили генерацию SBOM на каждом CI-прогоне: для Python используем syft, для Node - тоже syft с поддержкой package-lock.json, для контейнерных образов - после сборки по финальному слою. SBOM в формате CycloneDX кладётся в артефакты CI и в отдельное хранилище рядом с образом.
Немедленной пользы от этого немного - пока SBOM просто лежит. Польза начинается, когда появляется CVE и нужно быстро ответить на вопрос «нас это касается?». Без SBOM это несколько часов ручной работы; с ним - запрос к базе и ответ за минуты. В ноябре уже пригодится - но это отдельная история.
Зеркалирование зависимостей
Прямая зависимость от публичного PyPI или npm в prod-pipeline - это доверие к внешней инфраструктуре без каких-либо гарантий. Подняли внутренний Nexus Repository с прокси-репозиториями для PyPI и npm. CI теперь смотрит только во внутренний реестр, а наружу ходит только Nexus по явному расписанию.
Это решает несколько проблем сразу: пакеты кешируются и доступны при падении внешнего реестра, есть точка контроля для политик (какие пакеты разрешены, какие заблокированы), и сборка становится воспроизводимой - через месяц тот же pip install даст те же байты.
Настройка заняла день. Неожиданная боль: несколько пакетов тянули зависимости напрямую из GitHub через git+https:// в requirements.txt. Это пришлось разобрать отдельно - либо заменить на версионированный PyPI-пакет, либо перенести в отдельный внутренний репозиторий.
Проверка артефактов через Sigstore
Sigstore - это инфраструктура для подписи и верификации артефактов без управления собственными ключами. Cosign (часть Sigstore) позволяет подписать контейнерный образ после сборки и верифицировать подпись перед деплоем. Подпись привязывается к identity CI-пайплайна через OIDC - никаких долгоживущих ключей, которые можно утащить.
Для образов клиента внедрили такую схему: сборка в CI -> подпись через cosign с OIDC-токеном GitLab -> push образа и подписи в Harbor -> при деплое в Kubernetes - верификация подписи через admission controller (Kyverno политика). Образ без валидной подписи не запускается.
Это не защищает от компрометации самого CI, но существенно поднимает планку: случайный или намеренно подменённый образ из внешнего источника просто не пройдёт.
Для npm и PyPI пакетов Sigstore тоже движется в эту сторону - npm уже публикует provenance для части пакетов, PyPI запустил поддержку Trusted Publishers. Пока это не повсеместно, поэтому для зависимостей основная защита - хеши в lock-файлах и проверка через pip install --require-hashes.
Что осталось
Честно - сделано примерно половина из того, что хочется.
Политики разрешённых пакетов в Nexus пока не настроены: зеркало работает, но не фильтрует. Это следующий шаг - поднять список явно запрещённых пакетов и добавить проверку новых зависимостей через review-процесс.
SBOM-сканирование на CVE автоматически в CI не запущено. Есть ручной процесс, нет автоматического gate'а. Grype подключить технически несложно - вопрос в том, как обрабатывать false positives, чтобы не заблокировать pipeline мусором.
Cargo (Rust) в заголовке поста не случайно - у того же клиента есть небольшой Rust-компонент, и supply chain там отдельная тема. cargo-audit запущен, crates.io не зеркалируется, а Sigstore для Rust-артефактов только начинает появляться. Пока это технический долг, обозначенный в отчёте.
Для аудита безопасности такой поэтапный подход - норма: сначала видимость (SBOM), потом контроль точек входа (зеркала), потом верификация артефактов. Не всё за один спринт, но каждый шаг снижает реальную поверхность атаки, а не только закрывает пункты в чеклисте.