У нас несколько проектов, и релизы выходят практически каждый день. В последнее время я всё чаще замечаю: собираемся выпустить новую версию, а вместо этого разбираемся с зависимостями.
Сценарий знакомый. Запускаем 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. Для их оценки нужно учитывать доступ к секретам, артефактам и недоверенным входным данным, а не только наличие пакета в работающем приложении.
Как я бы перестроил процесс
Прежде всего я разделил бы обнаружение уязвимостей и решение о выпуске. К моменту деплоя команда должна по возможности уже знать о проблеме и понимать, что с ней делать.
- Проверять зависимости заранее. Запускать аудит при их изменении и регулярно по расписанию. Неизменный lock-файл не повод пропускать проверку: новые сведения появляются независимо от наших коммитов.
- Оценивать применимость. Разбирать путь до уязвимого пакета, условия эксплуатации, доступность компонента извне и возможный ущерб. Если данных недостаточно, так и записывать, а не объявлять риск нулевым.
- Обновлять отдельно от функциональных релизов. Выделять изменения зависимостей в понятные задачи с тестами и возможностью отката. Не запускать бездумно
npm audit fix --forceпрямо перед production: эта команда может выйти за заявленные диапазоны зависимостей, включая несовместимые обновления. - Фиксировать решения. Если риск временно принимается, записывать обоснование, ответственного, срок пересмотра и условия остановки релиза. Такое решение не должно превращаться в бессрочное исключение, о котором все забыли.
- Выпускать проверенный артефакт. Выкладывать ту сборку, которая прошла проверки, вместо новой сборки непосредственно в момент переключения production. При этом учитывать свежие критические сведения и для уже собранного артефакта: его неизменность не делает его автоматически безопасным.
Это мой вариант организации работы, а не предложение ослабить защиту. Возможность остановить релиз должна сохраниться. Менять я бы стал прежде всего момент, когда мы узнаём о проблеме, и порядок её разбора.
Что измерять
Число предупреждений я бы оставил в отчёте, но для управления работой смотрел бы ещё на несколько показателей:
- время от обнаружения применимой уязвимости до её устранения;
- количество релизов, остановленных аудитом;
- дополнительное время, которое разбор зависимостей добавляет к выпуску;
- число повторных разборов одной и той же проблемы в разных проектах.
Последний пункт особенно интересен, когда проектов несколько. Если причина общая, хочется один раз разобраться в ней основательно, а затем проверить условия каждого приложения, а не начинать расследование с нуля.
Вместо вывода
ИИ способен помогать в поиске ошибок. Насколько именно он увеличивает поток предупреждений в наших проектах, я пока не знаю. Но откладывать разбор всех находок до следующего деплоя мне уже кажется плохой привычкой.
Я бы хотел видеть к выпуску понятную картину: что обнаружено, к каким компонентам это относится, что исправлено и какие риски ещё остаются. Тогда решение об остановке релиза можно объяснить конкретной проблемой.
Безопасность не должна превращаться в формальность. Но и деплой не должен каждый день превращаться в неожиданное расследование.
Если у вас выпуск обновлений тоже регулярно застревает на зависимостях, напишите мне. Можно начать с разбора текущего CI и правил, по которым команда решает, что блокирует релиз.
