Как проверить зависимости проекта на уязвимости
Как обнаружить уязвимые зависимости, оценить риски, обновить библиотеки и автоматизировать проверки безопасности в CI с помощью npm audit, pip-audit и OSV-Scanner.
Перед релизом вы запускаете проверку зависимостей и находите чуждую этому проекту библиотеку. Её никто в команде не добавлял напрямую: она пришла как транзитивная зависимость одного из пакетов, который вы сами подключили. Теперь нужно выяснить, используется ли уязвимый код из этой библиотеки в приложении. Для этого придется определить, через какую цепочку зависимостей была установлена эта библиотека и что именно необходимо менять: саму библиотеку или пакет выше по цепочке, подключающий её. Так исправление, лежащее в обновлённом lock-файле репозитория, дойдёт до продакшена.
Centicore Group разбирает этот процесс: как собрать список прямых и вложенных зависимостей и получить отчёт, найдя в нём затронутые компоненты и определив приоритет находок. Дальше покажем, как обновить зависимости и проверить результат, а также встроить проверку в CI. Основной пример будет на npm. Для Python и репозиториев с несколькими языками рассмотрим отдельные инструменты. Отдельно рассмотрим расширения базы данных и системные пакеты внутри контейнера — их легко упустить, если проверять только зависимости кода.
Как определить, какие зависимости проверять
Сканер проверяет то, что вы ему укажете, поэтому сначала стоит понять, из чего вообще состоят зависимости проекта. В проекте есть прямые зависимости, которые вы добавляете, и транзитивные, которые требуют сами библиотеки. Например, SDK для работы с внешним сервисом может использовать HTTP-клиент, а тот подключать дополнительные пакеты. Уязвимость может оказаться на любом уровне этой цепочки.
Для проверки нужны точные версии. В манифесте проекта часто указаны допустимые диапазоны: при установке менеджер пакетов выбирает подходящие версии из них. Lock-файл фиксирует результат разрешения зависимостей. Например, package-lock.json описывает конкретное дерево пакетов, которое npm может воспроизвести при установке.
Начните с коммита и lock-файла, из которых собираете релиз. Если проверяете уже работающий сервис, установите, какая сборка развернута в окружении. Отчёт по сегодняшней основной ветке может описывать другой набор библиотек.
Для монорепозитория составьте перечень сервисов и их lock-файлов. Проверка каталога одного сервиса ничего не говорит о соседнем приложении с собственными зависимостями. Сверьте этот перечень с файлами, которые обнаружил сканер: пропущенный сервис способен остаться за пределами отчёта.
Инструменты разработки тоже заслуживают проверки. Уязвимость в сборщике или тестовом сервере может затронуть CI и рабочую машину, даже если пакет отсутствует в продакшене. Разделение на рабочие и сборочные зависимости помогает понять, относится ли находка к пакету из продакшена или только из сборки и CI.
У контейнерного приложения есть дополнительный уровень: системные пакеты внутри образа. Для их проверки нужен сканер образов, например Trivy. Проверяйте именно образ, который собираетесь выпускать: аудит библиотек из репозитория не охватывает всю его файловую систему.
У базы данных есть похожий слепой угол: расширения вроде PostGIS или pgcrypto в Postgres. Они не видны ни одному из сканеров зависимостей, которые мы разбирали выше. Чтобы заглянуть за этот угол, выполните запрос SELECT extname, extversion FROM pg_extension;. Он покажет то, что реально подключено к базе, а не то, что упомянуто в документации проекта. Если вы сами собираете образ СУБД и расширение поставлено в нём системным пакетом, то для его проверки будет достаточно того же сканера образов, который проверяет остальные системные пакеты. В случае с управляемой базой — RDS, Cloud SQL — расширения привязаны к версии движка и обновляются вместе с ней через отдельную процедуру апгрейда. До файловой системы такой базы сканер образов просто не доберётся.
Как запустить проверку для своего стека
Сканеры зависимостей сопоставляют найденные пакеты и версии с базами известных уязвимостей. Такой анализ входит в SCA, Software Composition Analysis, то есть анализ состава программного обеспечения. Он дает исходные данные для разбора риска: название пакета, затронутые версии и описание проблемы.
Пустой отчёт означает, что сканер не нашел совпадений с доступной ему базой в распознанных зависимостях. Новую, еще не описанную уязвимость такой поиск может пропустить.
Ниже приведены команды для установленных инструментов. Выполняйте их из каталога нужного проекта. Инструкции по установке и поддерживаемым форматам доступны в документации каждого сканера.
Node.js и npm
Для проекта с package-lock.json используйте встроенную команду npm audit:
Параметр include=dev явно включает зависимости разработки. Команда отправляет сведения о пакетах в настроенный registry, поэтому для закрытого проекта проверьте, какой реестр использует npm и поддерживает ли он аудит. Сама проверка без подкоманды fix зависимости не обновляет.
Отчёт можно сохранить в JSON:
При обнаружении уязвимостей команда по умолчанию возвращает ненулевой код завершения. В CI это может остановить шаг; сохранение отчёта нужно предусмотреть и для такого результата.
Python
pip-audit проверяет зависимости Python. Для файла requirements.txt команда выглядит так:
Если файл содержит диапазоны версий, инструмент разрешает зависимости при проверке. Получившийся набор может отличаться от установленного в вашем приложении. Чтобы проверить конкретную сборку, используйте полный список зафиксированных версий либо воспроизведите её виртуальное окружение и запустите аудит в нем:
Учитывайте модель безопасности инструмента: сам pip-audit предупреждает, что для диапазонов версий он разрешает зависимости так же, как это делает pip install, то есть выполняет код устанавливаемых пакетов. Поэтому для непроверенного репозитория запускайте аудит в изолированном окружении без рабочих секретов: pip-audit не заменяет проверку репозитория перед установкой.
Репозиторий с несколькими языками
OSV-Scanner умеет искать поддерживаемые lock-файлы в каталогах проекта. Для рекурсивной проверки используйте синтаксис второй версии инструмента:
После запуска проверьте, какие файлы сканер распознал. Поддерживаемые экосистемы и форматы перечислены в документации OSV-Scanner. Если нужного формата нет, потребуется другой способ получить состав зависимостей или отдельный инструмент.
Как разобраться в находках
Начните с одной записи отчёта и откройте связанное с ней описание уязвимости, security advisory. В этом описании ищите условия эксплуатации, затронутые версии и доступное исправление. Номер CVE или GHSA помогает однозначно сослаться на проблему при обсуждении и заведении задачи.
Дальше установите путь пакета в проект. Для npm это можно сделать через npm explain. Например, если отчёт указывает на minimist, выполните:
Команда покажет, почему пакет установлен. Если его подключает другая библиотека, исправление может потребовать обновить именно её. Проверяйте каждую найденную версию: в одном дереве могут одновременно находиться несколько экземпляров пакета.
После этого сопоставьте описание уязвимости с устройством приложения — ответьте по порядку на несколько вопросов:
5 сервисов · 2 столбца
Листайте столбцы →Смахните влевоВсе 2 столбца
Предположим, advisory описывает отказ в обслуживании при разборе специально подготовленного файла. Если ваш API принимает файлы от пользователей и передает их этому парсеру, условия для атаки могут совпасть. Если парсер обрабатывает только встроенный справочник при сборке, путь эксплуатации другой. В таком случае проверьте, кто может изменить справочник и что произойдет при обработке этих данных в CI.
Пометка devDependency сама по себе не закрывает вопрос. Функцию может вызывать и промежуточная библиотека, которую не покажет простой текстовый поиск по коду: такой поиск подтверждает лишь то, что он нашел, а не то, что вызова нет нигде. Если подтвердить применимость пока не удалось, зафиксируйте неопределенность и назначьте дальнейшую проверку.
Расстановка приоритетов
Уровень Critical или High помогает начать сортировку. Эту шкалу задаёт CVSS — Common Vulnerability Scoring System, система оценки серьёзности уязвимостей. Но очередность исправлений зависит и от условий вашего проекта. Доступный извне сервис с подходящим сценарием эксплуатации требует другого решения, чем изолированный инструмент с ограниченными входными данными.
Дополнительным сигналом может служить EPSS, Exploit Prediction Scoring System, то есть модель прогноза эксплуатации. Она оценивает вероятность эксплуатации опубликованной CVE в реальных атаках в ближайшие 30 дней и описывает общую ситуацию вокруг уязвимости; применимость к вашему приложению нужно устанавливать отдельно.
Для каждой существенной находки заведите задачу: пакет и версия, идентификатор уязвимости, затронутый сервис, условия эксплуатации, ответственный и срок. Если решили отложить обновление, добавьте обоснование и дату пересмотра. Так следующий разработчик сможет проверить решение, когда изменятся код, настройки или сведения об угрозе.
Как исправить зависимость и проверить результат
Обновление начинайте в отдельной ветке. Сначала воспроизведите зависимости исходной сборки. Для npm используйте npm ci, которая устанавливает дерево из lock-файла и сообщает об ошибке при его расхождении с package.json:
Затем посмотрите, какие автоматические исправления предлагает npm:
Если предлагаемые изменения подходят, примените их:
Автоматическое исправление устанавливает совместимые обновления, но часть проблем требует ручной работы. Изучите изменения в package.json и package-lock.json. Проверьте, обновилась ли нужная транзитивная зависимость и сохранились ли другие уязвимые экземпляры.
Параметр force разрешает npm выходить за заданные диапазоны версий, включая переход на другую мажорную версию прямой зависимости. Применяйте такие изменения после проверки совместимости и требований к миграции. Зеленый отчёт не подтверждает, что приложение продолжает правильно работать.
После обновления соберите проект и запустите его тесты. Особое внимание уделите сценариям, которые используют измененную библиотеку: обработке запросов, чтению файлов, авторизации или другим затронутым операциям. Затем повторите аудит и проверьте состав итоговой сборки.
Если готового исправления нет, изучите рекомендации автора пакета. В зависимости от проблемы можно отключить уязвимую функцию, ограничить доступ к ней, заменить библиотеку или удалить зависимость. Временная мера должна перекрывать конкретный сценарий эксплуатации. Её работоспособность тоже нужно проверить.
Завершайте задачу после выпуска исправленной сборки в затронутое окружение. Пока сервис работает со старым образом или артефактом, обновленный lock-файл в репозитории не меняет его зависимости.
Как встроить проверку в CI
Разделите запуск на два режима. Первый — проверка изменений в пул-реквесте, помогающая обнаружить уязвимость в добавленной или обновленной зависимости. Второй — регулярный аудит полного состава проекта для нахождения проблем, сведения о которых появились уже после последнего коммита.
Например, dependency review в GitHub показывает изменения зависимостей в пул-реквесте. Соответствующий GitHub Action позволяет завершать проверку с ошибкой, если изменение добавляет известную уязвимость. Полный аудит по расписанию дополняет такую проверку.
Для npm можно задать порог, при котором задача CI завершится с ошибкой:
При находках High и Critical шаг CI завершится с ошибкой. Находки меньшей тяжести остаются в отчёте: параметр меняет условие завершения команды. Это пример настройки, которую нужно согласовать с правилами вашей команды.
Если в проекте накопились старые проблемы, назначьте им владельцев и сроки, а новые находки рассматривайте при ревью изменений. Для временных исключений храните идентификатор уязвимости, конкретную зависимость, обоснование и дату окончания. Постоянное игнорирование целой категории находок лишает проверку смысла.
Различайте обнаруженную уязвимость и неудачный запуск сканера. Ошибка доступа к реестру, недоступная база или нераспознанный файл означают, что проверка не дала нужного результата. Сохраняйте логи и отчёты при сбоях. Не превращайте любую ошибку команды в успешный шаг CI.
Для выпущенных сборок полезно сохранять SBOM, Software Bill of Materials: машиночитаемый перечень компонентов и их версий. Привяжите его к конкретному артефакту или образу. Когда появится новое advisory, этот список поможет найти затронутые релизы. Возможность читать SBOM есть, например, у OSV-Scanner.
Что проверить перед закрытием задачи
Отчёт относится к нужному коммиту или сборке, и все сервисы и файлы зависимостей попали под проверку.Для существенных находок разобраны путь зависимости и условия эксплуатации, а неопределенность явно зафиксирована.Изменения версий прошли ревью, сборку и тесты затронутых сценариев.Повторный аудит подтвердил исправление. Для оставшихся находок есть ответственные, сроки и обоснованные временные меры.Исправленная сборка развернута в затронутом окружении, её состав сохранен.CI проверяет изменения зависимостей и запускает полный аудит по расписанию, а ошибки сканера остаются видимыми.
