Что делать, если зависимость скомпрометирована: пошаговый порядок удаления и восстановления безопасности проекта

Скомпрометированный пакет в зависимостях — это не просто уязвимость, а активная угроза: вредоносный код уже находится в вашей среде сборки или продакшене. Ситуация требует не патчинга, а немедленного изоляции, удаления и расследования. Ниже — порядок действий, применимый к любому стеку (npm, PyPI, Maven, Go, NuGet, Cargo и др.), с учётом различий в инструментах.

1. Мгновенная реакция: изоляция и остановка распространения

Первые минуты после обнаружения определяют масштаб инцидента. Действуйте в таком порядке:

  1. Остановите все CI/CD-пайплайны, которые могут скачивать или собирать проект. Отключите автоматические деплои, мержи и релизы.
  2. Изолируйте артефакты: не публикуйте новые версии, не пушьте образы в реестры, не раздавайте артефакты тестировщикам.
  3. Зафиксируйте версию.lock-файлов (package-lock.json, yarn.lock, poetry.lock, go.sum, Cargo.lock, pom.xml с versions). Это единственный достоверный источник того, что именно было собрано.
  4. Определите вектор попадания: прямая зависимость (указанная в manifest) или транзитивная (подтянута другой библиотекой). Это меняет тактику удаления.

Не пытайтесь «починить» код пакета или форкнуть его на лету — вы не знаете полный масштаб изменений. Удаление — приоритет.

2. Идентификация и инвентаризация

Прежде чем удалять, точно определите, что и где находится. Используйте нативные команды менеджеров пакетов, а не только сканеры:

  • npm / yarn / pnpm: npm ls или yarn why — покажет полное дерево зависимостей и кто тянет пакет.
  • Python (pip / poetry / uv): pip show -f , poetry show —tree, uv tree.
  • Maven / Gradle: mvn dependency:tree -Dincludes=*::* или gradle dependencyInsight —dependency .
  • Go: go mod why (Go 1.21+).
  • Rust (Cargo): cargo tree -i .
  • .NET (NuGet): dotnet list package —include-transitive с фильтром.

Запишите: точное имя пакета, скомпрометированные версии, все пути попадания (прямые и транзитивные), проекты и сервисы, где пакет присутствует. Если пакет попал в Docker-образы — перечислите теги образов.

3. Оценка воздействия (Blast Radius)

Не все компрометации одинаковы. Классифицируйте инцидент, чтобы выбрать тактику:

Тип компрометации Признаки Действие
Вредоносный код в postinstall / setup.py / build-скриптах Выполняется при установке/сборке; может красть токены, менять файлы, качать пейлоады Считайте скомпрометированной всю среду сборки (CI-раннеры, локальные машины разработчиков). Требуется ротация секретов и пересборка с чистого состояния.
Вредоносный код в runtime (импортируемый модуль) Активируется при запуске приложения Скомпрометированы все среды, где запускалось приложение с этой версией. Нужен аудит логов, дампов памяти, сетевых соединений.
Прототипная загрязнение / supply-chain атака через maintainer Легитимный пакет обновлён злоумышленником с правами публикации Проверьте все версии в диапазоне. Часто затрагивается только одна версия — зафиксируйте безопасную версию ниже или выше.
Типографическая ошибка (typosquatting) / dependency confusion Пакет с похожим именем подсовывается вместо внутреннего Проверьте настройки реестров (scope, private registry priority). Удалите чужой пакет, добавьте внутренний в allow-list.

Если пакет выполнял код при установке — считайте скомпрометированными все секреты, доступные в среде сборки (CI_VARIABLES, GitHub Actions secrets, AWS credentials, npm tokens). Планируйте их ротацию параллельно с удалением пакета.

4. Удаление пакета: конкретные шаги по экосистемам

npm / pnpm / yarn

  1. Если пакет — прямая зависимость: удалите из package.json (npm uninstall / pnpm remove ).
  2. Если транзитивная: используйте overrides (npm 8.3+, pnpm) или resolutions (yarn v1) в package.json, чтобы форсировать безопасную версию или исключить пакет полностью:
    «overrides»: {
    «compromised-package»: «npm:safe-alternative@^1.0.0»
    }
  3. Выполните npm install / pnpm install / yarn install с флагом —prefer-offline или после очистки кэша (npm cache clean —force, pnpm store prune).
  4. Проверьте: npm ls compromised-package должен вернуть пустой результат.

Python (pip, poetry, uv)

  1. Прямая зависимость: удалите из pyproject.toml / requirements.txt.
  2. Транзитивная: в Poetry добавьте в [tool.poetry.dependencies] строку compromised-package = «!=» или зафиксируйте безопасную версию родительского пакета, который тянет вредоносный.
  3. В uv / pip-tools: используйте —exclude или зафиксируйте в requirements.txt безопасные версии всех зависимостей.
  4. Пересоздайте виртуальное окружение целиком (rm -rf .venv && python -m venv .venv) — pip не гарантирует чистоту при in-place обновлении.

Maven / Gradle

  1. Добавьте исключение в pom.xml или build.gradle:
    bad.groupbad-artifact
  2. Или используйте dependencyManagement / resolutionStrategy.force для переопределения версии на безопасную.
  3. Очистите локальный репозиторий: rm -rf ~/.m2/repository/bad/group.
  4. Запустите сборку с —offline после проверки, что все артефакты разрешены корректно.

Go

  1. В go.mod замените требуемую версию на безопасную или используйте replace директиву на форк/альтернативу:
    replace bad/module => safe/fork v1.2.3-fixed
  2. Выполните go mod tidy и go mod verify.
  3. Очистите кэш модулей: go clean -modcache.

Rust (Cargo)

  1. В Cargo.toml добавьте patch секцию или используйте [replace] в .cargo/config.toml для подмены на безопасную версию/форк.
  2. Выполните cargo update -p compromised-package —precise .
  3. Проверьте: cargo tree -p compromised-package не должен показывать пакет.

.NET (NuGet)

  1. Удалите PackageReference или добавьте для блокировки.
  2. Или используйте Directory.Packages.props с (Central Package Management).
  3. Очистите кэш: dotnet nuget locals all —clear.

5. Поиск и валидация замены

Удаление без замены ломает сборку. Подход к выбору альтернативы:

  • Официальный патч: если мейнтейнеры выпустили исправленную версию — обновитесь на неё после проверки changelog и diff коммитов. Убедитесь, что фикс именно устраняет вредоносный код, а не просто «убирает подозрительный файл».
  • Форк сообщества: часто появляются форки с суффиксами -safe, -fixed, -patched. Проверьте: кто автор форка, есть ли CI, подписаны ли коммиты, совпадает ли API.
  • Альтернативная библиотека: если пакет мал и функционал стандартный — перепишите на стандартную библиотеку или популярный аналог (например, заменить скомпрометированный event-stream на нативные стримы).
  • Внутренняя копия (vendoring): для критических маленьких утилит — скопируйте код в свой репозиторий под своим именем, уберите лишнее, добавьте тесты. Это исключает будущие supply-chain риски для этого кода.

Перед внедрением замены: запустите полный набор тестов (unit, integration, contract), проверьте типизацию (TypeScript, mypy, go vet), прогоните линтеры и SAST-сканеры. Если нет покрытия — напишите минимальные smoke-тесты для затронутых путей.

6. Очистка артефактов и сред

Удаление из манифеста не убирает пакет из уже собранных артефактов:

  • Docker-образы: пересоберите все образы с обновлёнными lock-файлами. Удалите старые теги из реестра (docker rmi, API реестра). Если образы подписаны (cosign, notation) — переподпишите новые.
  • CI-кэш: инвалидируйте кэши зависимостей (GitHub Actions cache, GitLab CI cache, CircleCI, BuildKit cache).
  • Локальные машины разработчиков: расшлите инструкцию: удалить node_modules, .venv, target, ~/.m2, ~/.cache/go-build, ~/.cargo, ~/.nuget и переустановить зависимости.
  • SBOM (Software Bill of Materials): перегенерируйте SBOM для всех релизов (Syft, CycloneDX, SPDX) и загрузите в хранилище/портал безопасности.

7. Ротация секретов и аудит следов

Если компрометированный пакет выполнял код при установке или рантайме — секреты утекли. Минимум к ротации:

  • CI/CD токены (GitHub PAT, GitLab CI tokens, Azure DevOps PAT).
  • Ключи доступа к облакам (AWS Access Keys, GCP Service Account keys, Azure SP secrets).
  • Токены реестров пакетов (npm token, PyPI token, Maven Central token, crates.io token, NuGet API key).
  • Секреты приложения: JWT signing keys, encryption keys, database passwords, API keys сторонних сервисов.
  • SSH-ключи и GPG-ключи, если были доступны в среде сборки.

Проверьте логи доступа к секретам (CloudTrail, Audit Logs, GitHub Security Log) на предмет аномальной активности за период с момента появления вредоносной версии. Ищите: незнакомые IP, нетипичные часы, доступ к ресурсам, которые пакет не должен был трогать.

8. Верификация: как убедиться, что пакет ушёл

Не верьте только успешной сборке. Проведите независимые проверки:

  1. Сборка в чистом окружении: временный контейнер / новая VM / чистый CI-раннер без кэшей. Клонируйте репозиторий, соберите, проверьте отсутствие пакета в артефактах.
  2. Сканирование артефактов: запустите Trivy, Grype, Syft, OWASP Dependency-Check на собранных образах и бинарниках. Они должны не находить CVE и не находить имя скомпрометированного пакета.
  3. Проверка lock-файлов: автоматизированный тест в CI, который парсит lock-файл и падает, если встречается запрещённый пакет/версия.
  4. Runtime-проверка (если применимо): eBPF-агенты (Tetragon, Falco) или профилирование импортов в staging — убедитесь, что модуль не загружается.

9. Типичные ошибки и как их избежать

  • Удалили из package.json, но не обновили lock-файл. Lock-файл — источник истины для CI. Всегда коммитьте обновлённый lock-файл в один коммит с манифестом.
  • Зафиксировали версию родительского пакета, но не проверили, не тянет ли он вредоносный пакет через другую транзитивную цепочку. Используйте npm ls / pipdeptree / go mod why после каждого изменения.
  • Использовали форк без аудита. Форк может содержать тот же вредоносный код или новый. Проверяйте diff от оригинала, историю коммитов, подписи.
  • Не очистили кэш пакетного менеджера. Менеджеры могут брать пакет из локального кэша даже после удаления из реестра. Очистка кэша обязательна.
  • Забыли про монорепозитории и общие библиотеки. Если пакет используется в shared-пакете — его нужно удалить там, пересобрать shared, обновить версию, и только потом обновлять потребителей.
  • Не проинформировали downstream-потребителей. Если вы публикуете библиотеку — выпустите патч-версию с исправлением, обновите CHANGELOG, поставьте тег security advisory (GitHub Security Advisory, OSV, CVE при необходимости).

10. Превентивные меры: чтобы не повторить

Инцидент — повод усилить защиту supply chain. Приоритетные меры:

  • Pinning версий + lock-файлы в VCS
  • Детерминированные сборки, исключение неявных обновлений
  • Dependabot / Renovate с lock-file-maintenance, npm ci, pip install —require-hashes
  • Проверка подписей и provenance (SLSA)
  • Гарантия, что пакет собран из проверенного исходного кода
  • npm attestations, PyPI trusted publishing, Sigstore/cosign, Go module proxy с верификацией
  • Приватный прокси/реестр с allow-list
  • Блокировка неизвестных пакетов, защита от dependency confusion
  • Verdaccio, Nexus, Artifactory, GitHub Packages с настройкой upstream priority
  • Автоматический аудит зависимостей в CI
  • Раннее обнаружение CVE и вредоносных пакетов
  • OSV-Scanner, Trivy, Snyk, Dependabot alerts, GitHub Dependency Review Action
  • Минимизация зависимостей (vendor small utils)
  • Меньше поверхности атаки, меньше транзитивных цепочек
  • Критерий: если пакет < 50 строк и без зависимостей — скопируйте код к себе
  • Политика обновлений: staging → canary → prod с задержкой
  • Время на реакцию сообщества при компрометации популярного пакета
  • Задержка 24–72 часа для non-critical обновлений, мониторинг OSV / GitHub Advisories
  • Мера Что даёт Инструменты / примеры

    11. Чек-лист для следующего инцидента (сохраните и используйте)

    1. [ ] Остановлены CI/CD, деплои, публикации.
    2. [ ] Зафиксированы lock-файлы, определены все пути попадания пакета.
    3. [ ] Классифицирован тип компрометации (build-time / runtime / typo / maintainer).
    4. [ ] Пакет удалён из манифестов, настроены overrides / exclusions / replace.
    5. [ ] Кэши менеджеров пакетов очищены, сборка проходит в чистом окружении.
    6. [ ] Найдена и провалидирована замена (патч / форк / альтернатива / vendoring).
    7. [ ] Пересобраны все артефакты (Docker, бинарники, SBOM).
    8. [ ] Инвалидированы CI-кэши, очищены локальные среды команды.
    9. [ ] Выполнена ротация всех секретов, доступных в среде сборки/рантайма.
    10. [ ] Проведен аудит логов доступа за период компрометации.
    11. [ ] Автоматизированные проверки (сканеры, lock-файл тесты) проходят зелёным.
    12. [ ] Downstream-потребители уведомлены / опубликован security advisory.
    13. [ ] Постмортем: корневая причина, уязвимость процесса, план улучшений.

    Резюме: главный принцип

    Скомпрометированный пакет — это инцидент безопасности, а не задача по обновлению зависимости. Порядок действий: изолировать → инвентаризировать → оценить ущерб → удалить радикально (с lock-файлами и кэшами) → заменить на проверенное → пересобрать всё → повернуть секреты → зафиксировать в автоматизации. Любой пропущенный шаг оставляет «дверь» открытой.

    Начните прямо сейчас с добавления в CI проверки lock-файлов на запрещённые пакеты и настройки приватного прокси с allow-list — это два мера, которые сэкономят часы при следующем инциденте.

    Материал носит информационный характер и не заменяет инцидент-респонс-план вашей организации или консультацию с командой информационной безопасности. При компрометации критических систем (финтех, медтех, гостайна) немедленно привлекайте профильных специалистов и следуйте регламентам регуляторов.

    PEFile.ru