Скомпрометированный пакет в зависимостях — это не просто уязвимость, а активная угроза: вредоносный код уже находится в вашей среде сборки или продакшене. Ситуация требует не патчинга, а немедленного изоляции, удаления и расследования. Ниже — порядок действий, применимый к любому стеку (npm, PyPI, Maven, Go, NuGet, Cargo и др.), с учётом различий в инструментах.
- 1. Мгновенная реакция: изоляция и остановка распространения
- 2. Идентификация и инвентаризация
- 3. Оценка воздействия (Blast Radius)
- 4. Удаление пакета: конкретные шаги по экосистемам
- npm / pnpm / yarn
- Python (pip, poetry, uv)
- Maven / Gradle
- Go
- Rust (Cargo)
- .NET (NuGet)
- 5. Поиск и валидация замены
- 6. Очистка артефактов и сред
- 7. Ротация секретов и аудит следов
- 8. Верификация: как убедиться, что пакет ушёл
- 9. Типичные ошибки и как их избежать
- 10. Превентивные меры: чтобы не повторить
- 11. Чек-лист для следующего инцидента (сохраните и используйте)
- Резюме: главный принцип
1. Мгновенная реакция: изоляция и остановка распространения
Первые минуты после обнаружения определяют масштаб инцидента. Действуйте в таком порядке:
- Остановите все CI/CD-пайплайны, которые могут скачивать или собирать проект. Отключите автоматические деплои, мержи и релизы.
- Изолируйте артефакты: не публикуйте новые версии, не пушьте образы в реестры, не раздавайте артефакты тестировщикам.
- Зафиксируйте версию.lock-файлов (package-lock.json, yarn.lock, poetry.lock, go.sum, Cargo.lock, pom.xml с versions). Это единственный достоверный источник того, что именно было собрано.
- Определите вектор попадания: прямая зависимость (указанная в 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
- Если пакет — прямая зависимость: удалите из package.json (npm uninstall
/ pnpm remove ). - Если транзитивная: используйте overrides (npm 8.3+, pnpm) или resolutions (yarn v1) в package.json, чтобы форсировать безопасную версию или исключить пакет полностью:
«overrides»: {
«compromised-package»: «npm:safe-alternative@^1.0.0»
} - Выполните npm install / pnpm install / yarn install с флагом —prefer-offline или после очистки кэша (npm cache clean —force, pnpm store prune).
- Проверьте: npm ls compromised-package должен вернуть пустой результат.
Python (pip, poetry, uv)
- Прямая зависимость: удалите из pyproject.toml / requirements.txt.
- Транзитивная: в Poetry добавьте в [tool.poetry.dependencies] строку compromised-package = «!=» или зафиксируйте безопасную версию родительского пакета, который тянет вредоносный.
- В uv / pip-tools: используйте —exclude или зафиксируйте в requirements.txt безопасные версии всех зависимостей.
- Пересоздайте виртуальное окружение целиком (rm -rf .venv && python -m venv .venv) — pip не гарантирует чистоту при in-place обновлении.
Maven / Gradle
- Добавьте исключение в pom.xml или build.gradle:
bad.group bad-artifact - Или используйте dependencyManagement / resolutionStrategy.force для переопределения версии на безопасную.
- Очистите локальный репозиторий: rm -rf ~/.m2/repository/bad/group.
- Запустите сборку с —offline после проверки, что все артефакты разрешены корректно.
Go
- В go.mod замените требуемую версию на безопасную или используйте replace директиву на форк/альтернативу:
replace bad/module => safe/fork v1.2.3-fixed - Выполните go mod tidy и go mod verify.
- Очистите кэш модулей: go clean -modcache.
Rust (Cargo)
- В Cargo.toml добавьте patch секцию или используйте [replace] в .cargo/config.toml для подмены на безопасную версию/форк.
- Выполните cargo update -p compromised-package —precise
. - Проверьте: cargo tree -p compromised-package не должен показывать пакет.
.NET (NuGet)
- Удалите PackageReference или добавьте
для блокировки. - Или используйте Directory.Packages.props с
(Central Package Management). - Очистите кэш: 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. Верификация: как убедиться, что пакет ушёл
Не верьте только успешной сборке. Проведите независимые проверки:
- Сборка в чистом окружении: временный контейнер / новая VM / чистый CI-раннер без кэшей. Клонируйте репозиторий, соберите, проверьте отсутствие пакета в артефактах.
- Сканирование артефактов: запустите Trivy, Grype, Syft, OWASP Dependency-Check на собранных образах и бинарниках. Они должны не находить CVE и не находить имя скомпрометированного пакета.
- Проверка lock-файлов: автоматизированный тест в CI, который парсит lock-файл и падает, если встречается запрещённый пакет/версия.
- 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. Приоритетные меры:
| Мера | Что даёт | Инструменты / примеры |
|---|---|---|
11. Чек-лист для следующего инцидента (сохраните и используйте)
- [ ] Остановлены CI/CD, деплои, публикации.
- [ ] Зафиксированы lock-файлы, определены все пути попадания пакета.
- [ ] Классифицирован тип компрометации (build-time / runtime / typo / maintainer).
- [ ] Пакет удалён из манифестов, настроены overrides / exclusions / replace.
- [ ] Кэши менеджеров пакетов очищены, сборка проходит в чистом окружении.
- [ ] Найдена и провалидирована замена (патч / форк / альтернатива / vendoring).
- [ ] Пересобраны все артефакты (Docker, бинарники, SBOM).
- [ ] Инвалидированы CI-кэши, очищены локальные среды команды.
- [ ] Выполнена ротация всех секретов, доступных в среде сборки/рантайма.
- [ ] Проведен аудит логов доступа за период компрометации.
- [ ] Автоматизированные проверки (сканеры, lock-файл тесты) проходят зелёным.
- [ ] Downstream-потребители уведомлены / опубликован security advisory.
- [ ] Постмортем: корневая причина, уязвимость процесса, план улучшений.
Резюме: главный принцип
Скомпрометированный пакет — это инцидент безопасности, а не задача по обновлению зависимости. Порядок действий: изолировать → инвентаризировать → оценить ущерб → удалить радикально (с lock-файлами и кэшами) → заменить на проверенное → пересобрать всё → повернуть секреты → зафиксировать в автоматизации. Любой пропущенный шаг оставляет «дверь» открытой.
Начните прямо сейчас с добавления в CI проверки lock-файлов на запрещённые пакеты и настройки приватного прокси с allow-list — это два мера, которые сэкономят часы при следующем инциденте.
Материал носит информационный характер и не заменяет инцидент-респонс-план вашей организации или консультацию с командой информационной безопасности. При компрометации критических систем (финтех, медтех, гостайна) немедленно привлекайте профильных специалистов и следуйте регламентам регуляторов.
