У нас несколько проектов, и релизы выходят практически каждый день. В последнее время я всё чаще замечаю: собираемся выпустить новую версию, а вместо этого разбираемся с зависимостями.

Сценарий знакомый. Запускаем CI, получаем сообщение об уязвимости, выпуск останавливается. Дальше выясняем, какой пакет затронут, откуда он появился, есть ли исправление и что придётся обновить вместе с ним.

Ещё вчера этой работы не было в плане релиза. Сегодня без неё нельзя двигаться дальше. Уязвимости растут как грибы, а продолжительность деплоя становится всё менее предсказуемой.

Почему прежние зависимости дают новые предупреждения

Возьмём Node.js и npm ci. Эта команда устанавливает зависимости по lock-файлу и при стандартной настройке audit=true запрашивает сведения об известных уязвимостях. Состав пакетов может остаться прежним, а отчёт об их безопасности — измениться.

Вчера определённая версия библиотеки не вызывала предупреждений. Сегодня исследователь опубликовал описание ошибки, сведения попали в базу аудита, и та же версия появилась в отчёте. Код библиотеки за ночь не изменился. Мы узнали о проблеме, которая уже существовала.

Чтобы проверить зависимости ещё раз, не нужно удалять node_modules и переустанавливать пакеты. Достаточно запустить npm audit.

Здесь важно различать предупреждение и остановку релиза. Само сообщение об уязвимости обычно не делает npm ci неуспешным. Блокировку задают правила CI: например, отдельный шаг npm audit --audit-level=high, который завершается с ненулевым кодом при находках уровня high или critical. Порог влияет на результат команды, а не убирает менее серьёзные находки из отчёта.

Такое поведение описано в документации npm ci и npm audit.

При чём здесь искусственный интеллект

Моя гипотеза: массовая доступность ИИ ускоряет поиск уязвимостей. Анализировать код, исследовать зависимости и проверять подозрительные места становится проще. Но я не измерял, какая доля предупреждений в наших проектах связана именно с этим.

Примеры реальных находок уже есть. В ноябре 2024 года команда Google Project Zero и Google DeepMind описала работу агента Big Sleep, который обнаружил уязвимость в SQLite до её попадания в официальный релиз. Это подтверждает возможности подхода, но не объясняет автоматически рост предупреждений в конкретном npm-проекте.

И уж точно из этого не следует, что за каждой новой записью в отчёте стоят хакеры, которым нечем заняться. Ошибки находят исследователи безопасности и разработчики открытых библиотек. Аудит сообщает об известных рисках, а не считает совершённые атаки.

Исправление всё равно требует времени. Находку нужно проверить, подготовить патч, выпустить новую версию, обновить зависимые приложения и убедиться, что ничего не сломалось.

Если находить проблемы мы будем быстрее, а обновлять проекты прежними темпами, очередь такой работы станет только длиннее.

Одна уязвимость — несколько остановленных проектов

В приложении есть не только пакеты, которые мы выбрали сами. У них тоже есть зависимости. Одна библиотека глубоко в этом дереве может использоваться сразу в нескольких наших проектах.

Кроме того, npm учитывает так называемые метауязвимости: зависимый пакет тоже попадает в отчёт, если его требования привязывают нас к уязвимым версиям другого пакета. Поэтому несколько строк отчёта не обязательно означают несколько независимых ошибок. Этот механизм отдельно разобран в документации npm audit.

Для команды работа от этого не исчезает. Нужно проверить цепочки зависимостей, совместимость обновлений и поведение каждого приложения. Но полезно хотя бы понимать, разбираем мы новую проблему или ещё одно проявление уже известной.

Почему автоматической блокировки недостаточно

Проверка безопасности в CI необходима. Однако если первое знакомство с новым предупреждением происходит непосредственно перед выкладкой, любой релиз рискует превратиться в экстренную работу.

Метка critical или high — повод внимательно разобраться, но она не описывает весь риск конкретного приложения. Используем ли мы уязвимую функцию? Может ли атакующий передать ей данные? Каковы последствия? Есть ли сведения о реальной эксплуатации? Доступно ли исправление?

Ошибка в обработчике входящих данных публичного API и проблема в инструменте, который работает только с доверенными локальными файлами, могут требовать разных действий. npm прямо рекомендует учитывать условия, от которых зависит применимость уязвимости.

При этом запись в devDependencies не означает, что пакет безопасен. Сборочные инструменты исполняют код в CI. Для их оценки нужно учитывать доступ к секретам, артефактам и недоверенным входным данным, а не только наличие пакета в работающем приложении.

Как я бы перестроил процесс

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

  1. Проверять зависимости заранее. Запускать аудит при их изменении и регулярно по расписанию. Неизменный lock-файл не повод пропускать проверку: новые сведения появляются независимо от наших коммитов.
  2. Оценивать применимость. Разбирать путь до уязвимого пакета, условия эксплуатации, доступность компонента извне и возможный ущерб. Если данных недостаточно, так и записывать, а не объявлять риск нулевым.
  3. Обновлять отдельно от функциональных релизов. Выделять изменения зависимостей в понятные задачи с тестами и возможностью отката. Не запускать бездумно npm audit fix --force прямо перед production: эта команда может выйти за заявленные диапазоны зависимостей, включая несовместимые обновления.
  4. Фиксировать решения. Если риск временно принимается, записывать обоснование, ответственного, срок пересмотра и условия остановки релиза. Такое решение не должно превращаться в бессрочное исключение, о котором все забыли.
  5. Выпускать проверенный артефакт. Выкладывать ту сборку, которая прошла проверки, вместо новой сборки непосредственно в момент переключения production. При этом учитывать свежие критические сведения и для уже собранного артефакта: его неизменность не делает его автоматически безопасным.

Это мой вариант организации работы, а не предложение ослабить защиту. Возможность остановить релиз должна сохраниться. Менять я бы стал прежде всего момент, когда мы узнаём о проблеме, и порядок её разбора.

Что измерять

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

  • время от обнаружения применимой уязвимости до её устранения;
  • количество релизов, остановленных аудитом;
  • дополнительное время, которое разбор зависимостей добавляет к выпуску;
  • число повторных разборов одной и той же проблемы в разных проектах.

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

Вместо вывода

ИИ способен помогать в поиске ошибок. Насколько именно он увеличивает поток предупреждений в наших проектах, я пока не знаю. Но откладывать разбор всех находок до следующего деплоя мне уже кажется плохой привычкой.

Я бы хотел видеть к выпуску понятную картину: что обнаружено, к каким компонентам это относится, что исправлено и какие риски ещё остаются. Тогда решение об остановке релиза можно объяснить конкретной проблемой.

Безопасность не должна превращаться в формальность. Но и деплой не должен каждый день превращаться в неожиданное расследование.


Если у вас выпуск обновлений тоже регулярно застревает на зависимостях, напишите мне. Можно начать с разбора текущего CI и правил, по которым команда решает, что блокирует релиз.