Современное веб-приложение тащит за собой от нескольких сотен до пары тысяч сторонних пакетов, и львиная доля этого объёма попадает в сборку транзитивно, без прямого решения команды. Каждый пакет это чужой код со своим графиком релизов, своими ошибками и своими мотивами сопровождающего. Отсюда вечная развилка: вести учёт версий руками, читая каждый список изменений, либо поставить сканер уязвимостей и отдать ему рутину. На практике выживает гибрид, но пропорция у него жёсткая: автоматика забирает девяносто процентов работы, человек забирает решения и ответственность. Разберём, где проходит эта граница и почему попытка удержать всё в голове проигрывает обеим крайностям.
Дерево зависимостей растёт быстрее внимания команды Прямые зависимости в манифесте это вершина пирамиды. Строка фреймворка в разделе dependencies тянет за собой ещё несколько десятков транзитивных пакетов, которые никто не выбирал, не читал и не обновлял осознанно. Символ ^ в записи версии разрешает всё семейство 4.x, тильда ограничивается патчами, и фактический состав сборки фиксирует только lock-файл. Удали его, и одна и та же команда установки приносит разные версии от запуска к запуску. CODE0 Команда npm ls --all | wc -l честно показывает масштаб. Проект с двумя сотнями строк в манифесте обычно содержит около тысячи узлов в полном дереве. Проверить глазами такую толщу невозможно физически, а уязвимости прячутся именно в транзитивном слое: утилита для сборки стилей, обёртка над парсером форматов, библиотека, которую использовали один раз в 2019 году и оставили в графе навсегда. Ручной контроль на этом объёме превращается в театр: список просматривают по верхним строчкам, а настоящая глубина остаётся без присмотра.
Есть и асимметрия доверия: прямые зависимости выбирает архитектор с обоснованием в ревью, транзитивные приносит реестр. Пакет четвёртого уровня вложенности попадает в сборку потому, что кто-то из сопровождающих чужой библиотеки где-то когда-то решил так удобнее. Никто в команде не читал его код, и это нормально: читать тысячу пакетов невозможно. Именно поэтому контроль смещается с чтения на автоматическую проверку.
Ручной учёт версий ломается примерно на двадцатом пакете Пока зависимостей десять, таблица в вики работает. Ответственный раз в неделю заходит в реестр, сверяет номера, накатывает изменения, пишет заметку. К двадцати пакетам процесс съедает полдня, к пятидесяти дисциплина кончается: сопровождающий уходит в отпуск, перед релизом обновления откладываются, мажорные версии копятся кварталами. Через полгода откладывания очередь превращается в проект. Подняться сразу на четыре старшие версии это уже не обновление, а миграция с переписыванием интеграций и ночными отладками. Показательный урок дала утечка у крупнейшего американского бюро кредитных историй в 2017 году: взлом прошёл через уязвимость Apache Struts, исправление для которой существовало задолго до инцидента. Никакого злого умысла, просто фикс ждал своей очереди в бэклоге, пока очередь не дошла до него снаружи. Процесс, в котором обновление зависит от свободного инженера и его памяти, гарантированно пропускает критичные заплатки именно тогда, когда ставить их нужно быстрее всего. Ошибка системная, а не человеческая: внимание это возобновляемый, но резко ограниченный ресурс.
Отдельная категория риска это заброшенные пакеты: версия стабильна, уязвимостей не значится, а сопровождающий пропал года три назад. Формально всё чисто, фактически сообщество больше не выпускает исправления, и первый же найденный дефект останется без заплатки навсегда. Такие пакеты требуют плана замены или собственного форка, и без сканера с метаданными реестра этот список даже не собрать.
Сканеры уязвимостей собирают полную картину за минуты Автоматические проверки закрывают главный пробел ручного метода: они смотрят весь граф целиком и сверяют его с постоянно обновляемыми базами описаний уязвимостей. Инструменты давно перестали быть экзотикой и встроились в каждый шаг поставки: CODE1 Каждая строка опирается на свой каталог. npm audit сверяет состав lock-файла с реестром пакетов и появился в шестой версии npm в 2018 году. pip-audit проверяет перечень Python-пакетов по открытым описаниям уязвимостей. cargo audit читает базу RustSec. govulncheck идёт дальше остальных: он строит граф вызовов Go-кода и оставляет в отчёте только те уязвимости, до которых приложение реально дотягивается. trivy закрывает и lock-файлы, и файловые системы, и образы контейнеров, а ещё умеет выгрузить перечень состава в формате CycloneDX или SPDX. Такой перечень, SBOM, всё чаще требуют заказчики и аудиторы при поставке, и собрать его руками уже никто не берётся.
В конвейере сканер живёт в двух режимах. На каждой ветке он снимает отчёт и не блокирует сборку, а на выпуске версии превращается в ограничитель: находка критичного уровня с подтверждённой эксплуатацией останавливает выпуск до разбора. Порог блокировки настраивают по приоритетам, иначе красная сборка на безобидном пакете приучает команду нажимать кнопку повторного запуска не глядя.
Второй рубеж это происхождение пакетов: подписи реестра и аттестаты сборки позволяют отличить официальную публикацию от подменённой. Крупные реестры добавляют к выпускам подтверждения provenance, и инструменты проверки сверяют подпись с ожидаемым источником. Механизм молодой, но направление ясное: доверять не факту наличия пакета, а подтверждённому пути его появления.
Половина шума отчёта это пакеты разработки: инструменты сборки и тестов не попадают на боевые серверы, и находки в них сортируются по отдельной, более мягкой шкале. Полное отключение проверки зависимостей разработки это тоже ошибка: инструменты сборки исполняют чужой код на машине инженера и в конвейере. Правильное решение отдельная политика для этой группы, а не общий выключатель.
Оценка CVSS показывает тяжесть проблемы по шкале до десяти, актуальная редакция стандарта 4.0 вышла в конце 2023 года. Каталог известных эксплуатируемых уязвимостей KEV и модель EPSS от сообщества специалистов по реагированию добавляют главное, чего в базовой оценке нет: вероятность того, что дыру начнут использовать на практике в ближайшие тридцать дней. Сканер сводит всё это в один отчёт и расставляет приоритеты, на которые у человека ушла бы неделя чтения списков изменений.
Запросы на обновления приходят по расписанию без напоминаний Второй слой автоматизации закрывает сам процесс обновления. Роботы вроде Renovate или Dependabot следят за новыми версиями и сами готовят ветку с изменённым манифестом и lock-файлом. Дальше работает обычный конвейер: тесты, сборка, слияние при зелёном проходе. Конфигурация задаёт скорость реакции и уровень шума: CODE2 Здесь патчи сливаются без человека, минорные обновления собираются в один запрос раз в неделю, а мажорные всегда идут через ревью. Отдельного упоминания заслуживает задержка на свежесть: правило minimumReleaseAge держит только что вышедший релиз на скамейке запасных несколько дней. История с популярной утилитой сжатия в марте 2024 года показала, зачем нужна пауза: закладка ждала внутри свежих выпусков, и её заметил инженер, которому лишние полсекунды при входе по ssh показались подозрительными. Окно охлаждения не спасает от всего на свете, но отсеивает львиную долю поспешных проблем: сломанные релизы всплывают в первые же дни, и очередь обновления их просто не получает. Кому доверить автоматическое слияние? Рабочая практика средней команды звучит так: авто для патчей и для зависимостей разработки, ревью для всего, что меняет старшую версию или попадает в прод. Правило одно: цена ошибки автоматического обновления должна быть ниже цены ручного труда на него. Если тесты достаточно плотные, конвейер делает обновления дешёвыми, и тогда частота слияний растёт без роста риска.
Отдельная настройка нужна приватным пакетам: внутренние библиотеки компании не живут в публичных реестрах, и сканер обязан видеть частный каталог, иначе половина графа уходит из-под проверки. Роботы подключаются к корпоративному реестру тем же токеном, что и сборка, и готовят ветки обновления внутренних библиотек по тем же правилам, что и открытых.
Приоритет определяет эксплуатация а не громкий счётчик Отчёт сканера на две сотни позиций пугает ровно до тех пор, пока его не рассортируешь. Критерий сортировки не числовой, а ситуативный: где стоит уязвимый пакет, дотягивается ли до него исполняемый код и есть ли подтверждённые случаи эксплуатации. Сравнение двух историй иллюстрирует разброс. Уязвимость в библиотеке журналирования Java с оценкой 10.0 и номером CVE-2021-44228 вспыхнула в декабре 2021 года, исправление вышло в первые дни, сканеры кричали хором, и большинство команд закрыло проблему за считаные сутки. Обратный пример: десятки высоких оценок в утилитах сборки и тестирования, которые никогда не попадают на боевые серверы и не касаются чужого ввода. Формально счётчик зашкаливает, практического риска нет. Сортировка сводится к трём вопросам: попадает ли пакет в прод, есть ли путь до уязвимого кода, подтверждена ли эксплуатация. Всё остальное очередь плановых обновлений.
Для каждой заблокированной находки пишется план отката: предыдущая версия пакета, шаги возврата, владелец. Обновление без пути назад это не исправление, а обмен известной уязвимостью на неизвестный инцидент. Хороший тестовый контур делает откат редкостью, но сам факт плана дисциплинирует: обновление не включают в релиз, пока не понятен способ вернуться.
Находки без исправления, характерные для старых образов и заброшенных пакетов, фиксируют отдельным списком с обоснованием и датой пересмотра. Молчаливое игнорирование превращает отчёт в обои, на которые никто не смотрит, и через месяц инструмент начнут обходить мимо.
Политика обновлений задаёт сроки реакции и роли Автоматика без правил это фейерверк уведомлений, который перестают открывать на второй неделе. Живая стратегия помещается в несколько строк и выглядит так: 1. уязвимость с подтверждённой эксплуатацией в исполнимом коде продакшена закрывается в течение трёх суток после выхода исправления; 2. высокая оценка без признаков эксплуатации попадает в ближайший плановый релиз, но не позже двух недель; 3. патчи сливаются автоматически после зелёного прохода тестов, минорные обновления собираются в одну ветку раз в неделю; 4. мажорные обновления критичных пакетов идут отдельной веткой с ответственным и тестовым контуром; 5. находки без исправления получают письменное обоснование и дату пересмотра, а не молчаливый игнор.
За каждый пункт отвечает роль, а не абстрактная команда: владелец сканера следит за базами уязвимостей и уровнем шума, владелец выпуска решает вопрос блокировки, сопровождающий пакета готовит ветку обновления. Имена в этих ролях меняются, сами роли обязаны существовать всегда, иначе политика превращается в документ без исполнения.