Практически любой современный проект состоит из чужого кода на девяносто с лишним процентов. Вы пишете бизнес-логику, а под ней лежат сотни библиотек, каждая из которых тянет свои зависимости, и так на несколько уровней вглубь. В типовом приложении на Node.js дерево зависимостей — это легко полторы тысячи пакетов. Вы не читали код ни одного из них.
Это нормальный способ разработки, отказываться от него бессмысленно. Но у него есть следствие: уязвимость в любом из этих полутора тысяч пакетов — это уязвимость в вашем продукте. И узнать о ней вы можете либо от сканера, либо от злоумышленника.
Разберём, как начать заниматься этим системно, если раньше не занимались.
Что такое CVE и почему номер сам по себе ничего не значит
CVE — это идентификатор конкретной уязвимости в публичном каталоге, вида CVE-2025-12345. Он не описывает опасность, он лишь означает: «такая проблема зарегистрирована и вот её описание».
Рядом обычно идёт оценка CVSS — число от нуля до десяти. Девять и выше принято называть критическим, семь-восемь — высоким, четыре-шесть — средним. Эта шкала полезна для сортировки, но абсолютно бесполезна как единственный критерий для действия, и вот почему.
CVSS оценивает уязвимость в вакууме — в предположении наиболее неблагоприятной конфигурации. Реальная опасность зависит от того, как компонент используется именно у вас. Критическая уязвимость удалённого выполнения кода в библиотеке, которая у вас работает только в скриптах сборки и никогда не обрабатывает пользовательский ввод, менее опасна, чем средняя уязвимость в парсере, через который проходит каждый входящий запрос.
Отсюда первое практическое правило: сканер даёт список кандидатов, приоритет расставляете вы.
Первый запуск: приготовьтесь к плохим новостям
Если проект живёт несколько лет и зависимости никогда не проверялись, первый отчёт будет удручающим. Двести-триста находок — обычное дело, из них десяток критических.
Здесь есть развилка, и большинство команд выбирают неверную ветку: пугаются, решают «разберёмся, когда будет время», и не возвращаются к этому никогда. Правильная ветка — принять, что разгрести всё сразу невозможно, и начать с фильтрации.
Первый фильтр — среда выполнения. Разделите зависимости на те, что попадают в продакшен, и те, что живут только в разработке и сборке: линтеры, тестовые фреймворки, сборщики. Уязвимость в dev-зависимости почти всегда терпит: чтобы её эксплуатировать, нужно уже иметь доступ к вашей машине или к пайплайну. Это сразу срезает половину списка.
Второй фильтр — достижимость. Уязвимость в функции, которую ваш код никогда не вызывает, эксплуатировать нельзя. Современные сканеры частично умеют это анализировать, но даже ручная проверка по описанию — «уязвим парсер XML, а мы XML не обрабатываем» — снимает значительную часть находок.
Третий фильтр — наличие эксплойта. Существуют публичные каталоги уязвимостей, которые реально используются в атаках. Если уязвимость есть в таком списке — она поднимается в приоритете независимо от формальной оценки. Если уязвимость теоретическая, без известных случаев эксплуатации, и требует сложных условий, она может подождать планового обновления.
После трёх фильтров из трёхсот находок обычно остаётся десять-пятнадцать, требующих внимания на этой неделе. Это уже подъёмный объём.
Транзитивные зависимости: главная головная боль
Больше всего вопросов вызывают уязвимости не в тех библиотеках, которые вы подключили, а в тех, которые подтянулись сами — на третьем-четвёртом уровне вложенности.
Здесь есть три сценария.
Лучший: авторы вашей прямой зависимости уже выпустили обновление, которое тянет исправленную версию. Обновляете верхний пакет — проблема уходит.
Средний: обновления нет, но менеджер пакетов позволяет принудительно поднять версию вложенной зависимости. В npm это resolutions или overrides, в других экосистемах есть аналоги. Работает, но требует проверки: вы меняете версию библиотеки в обход её потребителя, и совместимость никто не гарантировал.
Худший: библиотека заброшена, обновления не будет. Тогда варианты — заменить зависимость, форкнуть и пропатчить самому, либо принять риск сознательно, задокументировав это решение. Последнее — совершенно легитимный вариант, если риск оценён. Нелегитимно — не заметить проблему вовсе.
Что сканировать, кроме кода приложения
Зависимости приложения — только первый слой. На практике уязвимости приходят ещё из трёх мест.
Системные пакеты на серверах. Ядро, openssl, системные библиотеки, веб-сервер. Здесь всё проще: обновления выходят регулярно, ставятся штатным менеджером пакетов, и половина проблем закрывается автоматическими обновлениями безопасности. Стоит только помнить, что часть обновлений требует перезапуска сервисов, а некоторые — перезагрузки. Обновлённый пакет openssl на диске при работающем старом процессе в памяти — это по-прежнему уязвимый сервис.
Образы контейнеров. Базовый образ, собранный полгода назад, содержит все уязвимости, накопившиеся с тех пор. Это самая частая находка при первом сканировании инфраструктуры: приложение свежее, а под ним слой системных библиотек годовалой давности.
Внешние сервисы и панели. CMS, админки баз данных, панели управления — всё, что смотрит в интернет. Здесь уязвимости эксплуатируются быстрее всего, потому что не требуют никакого доступа внутрь.
Как встроить это в работу, чтобы не забросить через месяц
Разовое сканирование бесполезно: через две недели картина изменится. Нужен процесс, и он должен быть максимально дешёвым в поддержке.
Первое место — момент сборки. Проверка зависимостей в пайплайне при каждом изменении, с падением сборки только на критических находках в продакшен-зависимостях. Ключевое слово «только»: если сборка будет падать на любой средней уязвимости в dev-пакете, разработчики через неделю добавят флаг, отключающий проверку, и вы получите видимость безопасности вместо безопасности.
Второе место — регулярное сканирование того, что уже работает. Это принципиально другая проверка: код не менялся, а уязвимости появились, потому что о них стало известно. Раз в сутки достаточно.
Третье — реакция. У находки должен быть владелец и срок. Простая матрица: критические в продакшене — сегодня, высокие — на этой неделе, остальные — в ближайший плановый цикл обновлений. Без сроков список превращается в вечнозелёный бэклог, куда никто не смотрит.
И отдельно — фиксация принятых рисков. Если вы решили не чинить конкретную находку, это решение должно быть записано вместе с обоснованием и датой пересмотра. Иначе через полгода никто не вспомнит, почему эта строчка в отчёте красная, и она будет тихо игнорироваться, включая тот случай, когда для неё появится рабочий эксплойт.
Про SBOM
Термин SBOM — это просто список всего, из чего состоит ваш продукт, в машиночитаемом формате. Звучит бюрократически, но у него есть очень практическая ценность.
Когда выходит громкая уязвимость в популярной библиотеке — как это было с Log4Shell, — первый вопрос звучит так: «она у нас вообще есть?». Без SBOM ответ занимает от нескольких часов до нескольких дней и включает ручной обход всех проектов. С SBOM — это поиск по списку за минуту.
Собирать его отдельно не нужно: инструменты сканирования формируют его попутно. Достаточно просто сохранять результат при каждом релизе.
Как это сделано у нас
В COSCIO сканирование зависимостей — отдельный модуль. Он забирает манифесты из подключённых репозиториев и с серверов, сверяет версии с публичными базами уязвимостей, раскладывает находки по уровням критичности и отделяет продакшен-зависимости от девелоперских. Результаты попадают в общую ленту инцидентов, а не в отдельный отчёт, который надо не забыть открыть.
Смысл именно в связке: сканер сам по себе — это ещё один инструмент, который через месяц перестают открывать. А находка, которая приходит в тот же поток, что и алерты по серверам, и живёт до закрытия, — уже часть рабочего процесса. Плюс к этому рядом лежит история: когда уязвимость появилась, когда её закрыли, сколько она провисела.
Полноценным аудитом безопасности это, конечно, не является. Сканер зависимостей не найдёт ошибку в вашей бизнес-логике, не проверит права доступа и не заменит пентест. Он закрывает конкретный, но очень массовый класс проблем — известные уязвимости в чужом коде, который вы используете.
Что сделать на этой неделе
Запустите сканирование на одном, самом важном проекте. Не на всех сразу — на одном.
Отфильтруйте результат: уберите dev-зависимости, оставьте то, что действительно попадает в продакшен. Посмотрите на оставшееся и найдите те находки, для которых существует публичный эксплойт. Скорее всего, их будет немного — вот с них и начните.
Затем добавьте проверку в сборку и поставьте ежедневное сканирование. И запишите на одну страницу, кто смотрит на результаты и в какие сроки реагирует.
Этого достаточно, чтобы перейти из состояния «мы не знаем, что у нас внутри» в состояние «мы знаем и управляем». Разница между этими состояниями гораздо больше, чем разница между хорошим и отличным сканером.