Supply chain Q1 2026: два npm-инцидента и как мы закрыли вход через Harbor + Cosign + Trivy
Два supply chain инцидента в Q1 2026 затронули популярные npm-пакеты. Рассказываем, как настроили Harbor, Cosign и Trivy для блокировки непроверенных образов до staging.
Атаки на цепочку поставок ПО продолжают расти - новые инциденты в open source в начале 2026
В январе и феврале 2026 года supply chain снова отметился. Два отдельных инцидента с npm: в первом - компрометация учётки мейнтейнера небольшой, но популярной утилиты для работы с конфигами, во втором - typosquatting под реальный пакет с дополнительной нагрузкой в postinstall-скрипте. Оба разобрали в публичных postmortem'ах, оба вектора не новые. Но каждый раз находятся команды, для которых инцидент становится поводом наконец разобраться со своим пайплайном.
У нас после тех событий выстроилась очередь из клиентов с примерно одинаковым вопросом: «мы тянем зависимости из публичного npm, у нас есть Harbor, но как конкретно настроить, чтобы непроверенный образ вообще не попал на staging?» Это хороший вопрос, и ответ на него конкретнее, чем кажется.
Где обычно стоит дыра
Типовая картина, которую мы видим при аудите в подобных случаях: Harbor стоит, образы в него пушатся, но на этом история заканчивается. Harbor как хранилище без политик - это просто S3 с красивым UI. Образ, собранный с вредоносной npm-зависимостью, спокойно ляжет рядом с легитимным, и ничего его не остановит.
Три вещи, которых не хватает в такой схеме:
- Сканирование на CVE и вредоносный код - Trivy умеет встроиться в Harbor как сканер; можно выставить политику, при которой образ с незакрытыми критическими уязвимостями вообще не будет помечен как доступный.
- Подпись образов - Cosign плюс Notation (Harbor поддерживает оба) позволяют подписать образ после сборки и верифицировать подпись перед деплоем. Образ, которого нет в transparency log, не проходит.
- Admission control в Kubernetes - без политики на уровне кластера все предыдущие шаги носят рекомендательный характер. Kyverno или OPA Gatekeeper закрывают последний рубеж.
Как мы это настраивали
Работали с одним из клиентов, у которых стек - GitLab CI, Harbor 2.11, Kubernetes 1.34 с Kyverno. Цель: образ, собранный из кода с вредоносной зависимостью, не должен попасть на staging-окружение.
Первый шаг - Trivy в Harbor. В настройках Harbor включается встроенный Trivy-сканер и выставляется порог: образы с критическими CVE без фикса блокируются при pull. Это работает через interceptor на уровне registry API. Важный нюанс: «блокируются при pull» - не при push. Образ попадает в Harbor, сканируется, и только после этого становится доступным или недоступным для pull. Первое время это удивляет команду - образ есть в реестре, но не тянется. Надо объяснять заранее.
Для npm-специфики Trivy умеет сканировать node_modules внутри образа и находить known-bad пакеты через базу advisories. Это не панацея от свежих атак - база обновляется с задержкой, - но typosquatting из февральского инцидента Trivy поймал бы: пакет уже был помечен как malicious в GHSA к моменту, когда мы это проверяли.
Второй шаг - Cosign и подпись в CI. После сборки образа и его прохождения Trivy-сканирования добавили stage подписи:
sign-image:
stage: security
image: registry.internal/devtools/cosign:2.4
needs: [build, trivy-scan]
id_tokens:
SIGSTORE_ID_TOKEN:
aud: sigstore
script:
- cosign sign --yes
--oidc-issuer ${CI_SERVER_URL}
--identity-token ${SIGSTORE_ID_TOKEN}
${HARBOR_HOST}/${CI_PROJECT_PATH}:${CI_COMMIT_SHA}
only:
- main
- /^release\/.*/
OIDC-интеграция с GitLab означает, что долгоживущих ключей нет - подпись привязана к identity конкретного pipeline. Подпись и прозрачность через Rekor - стандартная схема для Cosign keyless.
Harbor 2.11 умеет хранить Cosign-подписи как OCI-артефакты рядом с образом. Ничего дополнительно настраивать не пришлось - подпись автоматически ассоциируется с дайджестом образа.
Третий шаг - Kyverno-политика. Без этого шага первые два - просто метаданные без последствий. Политика запрещает запуск Pod'ов с образами без валидной Cosign-подписи:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: check-image-signature
match:
any:
- resources:
kinds: [Pod]
namespaces: [staging, production]
verifyImages:
- imageReferences:
- "harbor.internal/*"
attestors:
- count: 1
entries:
- keyless:
issuer: "https://gitlab.internal"
subject: "https://gitlab.internal/org/*"
rekor:
url: "https://rekor.sigstore.dev"
Namespace staging и production - явно. Dev-окружение намеренно оставили без принудительной верификации: разработчики локально собирают образы, которые не проходят через CI-подпись, и блокировать dev слишком болезненно.
Что получилось и что не получилось
Схема работает. Образ из вредоносного npm-пакета типа февральского инцидента не попадёт на staging по двум причинам: Trivy поймает known-bad пакет при сканировании в Harbor, и даже если Trivy пропустит (база устарела) - образ не подписан через легитимный CI-pipeline, и Kyverno его заблокирует.
Но честно про ограничения:
- Latency базы Trivy - новый вредоносный пакет первые несколько дней после публикации не будет в базе. Это не недостаток Trivy, это природа threat intel.
- Cosign keyless через публичный Rekor - часть клиентов не готова к тому, что metadata о подписях уходит во внешний публичный лог. Для них нужен self-hosted Rekor, а это отдельная инфраструктура.
- Dev-namespace дыра - да, мы её оставили осознанно, но это компромисс. Если в dev попадёт контейнер с чем-то плохим, он может навредить dev-данным или CI-пайплайну. Идеальным решением был бы отдельный, менее строгий Kyverno-профиль для dev, а не полное отсутствие политики.
Supply chain - это не задача, которую решают один раз. Это постоянный процесс: следить за новыми векторами, проверять, что существующие контроли их покрывают. После каждого публичного инцидента мы проходимся по своим клиентам и смотрим, что нужно поправить. Иногда это быстро, иногда - как в этот раз - превращается в нормальный проектный спринт.