Один npm-пакет — и релиз встал: как мы защищали supply chain

Alex.Morozov

New member
Пятница, вечер, я нажимаю кнопку релиза — и пайплайн падает на этапе установки зависимостей. Это был не тот приятный случай, когда сломался тест и сразу понятно, что править. Это был тот, когда сборка встала колом, а причина пряталась на три уровня ниже в дереве зависимостей, куда я до этого вечера ни разу в жизни не заглядывал. Сижу, смотрю на красный лог и понимаю, что релиз встал из-за одной строчки в чужом package.json.

Началось всё буднично. У нас стояли диапазоны версий с крышечкой, а lock-файл обновлялся через раз: зачем, у нас же всё работает. В тот вечер один транзитивный пакет выкатил патч, и вместе с ним приехал пост-инсталляционный скрипт, который при установке тянул бинарник извне. Наш сканер в CI такое поведение зарубил, деплой не прошёл, дежурный получил алерт. Формально система сработала идеально. Фактически мы потеряли вечер и полночи на разбор, потому что никто не понимал, откуда вообще взялся этот пакет.

Дальше было самое неприятное — не техническое, а организационное. Мы часа два выясняли, кто отвечает за эту зависимость. Никто. Она пришла как зависимость зависимости зависимости, её тянули три пакета в нашем дереве, и ни один человек в команде не помнил, чтобы мы её вообще осознанно добавляли. Зато все помнили фразу «да это же стандартный пакет, его все используют». Вот так и появляется дыра: не через взлом периметра, а через привычку не смотреть, что именно ты ставишь.

После инцидента мы переписали правила работы с зависимостями и, честно говоря, это оказалось полезнее, чем десяток внутренних презентаций про безопасность. Lock-файл теперь живёт в репозитории и обновляется осознанно, установка идёт только строго по нему, а не по диапазонам. В CI отключены lifecycle-скрипты зависимостей: нам не нужен чужой код, который что-то выполняет при установке. Сборка идёт в изолированном контейнере без свободного доступа в интернет, кроме приватного прокси-реестра с белым списком пакетов. Токены для публикации — минимально возможные по правам и короткоживущие, а не вечный пароль в переменной окружения.

Дальше — проверки, которые раньше казались бюрократией, а теперь кажутся базой. Сканирование зависимостей на каждый merge request, причём с блокировкой критвитых и высоких уязвимостей. Формирование SBOM на каждый релиз, чтобы через полгода можно было за минуту ответить на вопрос «а этот пакет у нас вообще есть?». Проверка происхождения артефактов и подписей там, где проект это поддерживает. И отдельное правило для новой зависимости: она добавляется только с обоснованием. Что она даёт, кто её поддерживает, сколько транзитивных пакетов она тянет за собой и почему нельзя решить задачу тридцатью строками своего кода.

Отдельно про процесс и людей, потому что именно здесь всё чаще и ломается. Обновления зависимостей присылает бот пачками, и мы разбираем их раз в неделю отдельным спокойным ритуалом, а не в панике посреди релиза. Сначала обновление проверяется в отдельной ветке, диффы читаются глазами, потом канареечный деплой на малую часть трафика, и только после этого полный выкат. Быстрый откат обязателен, иначе вся эта аккуратность превращается в лотерею. И да, мы научились удалять зависимости. Это самый недооценённый приём в защите supply chain.

Если соберу это в короткие советы для читателей, получится примерно так. Держите lock-файл в репозитории и устанавливайте строго по нему. Отключайте выполнение скриптов зависимостей в сборочной среде. Ходите в публичный реестр только через свой прокси с белым списком. Встройте сканирование уязвимостей и сборку SBOM прямо в пайплайн, а не в отдельный квартальный отчёт. Давайте токенам минимум прав. Проверяйте происхождение и подписи артефактов. Регулярно спрашивайте себя, зачем вам каждый пакет в дереве, и без жалости выкидывайте лишнее. Supply chain — это не одна галочка в чек-листе, а ежедневная гигиена, и стоит она дешевле, чем один сорванный релиз.

Сейчас я вспоминаю тот вечер почти с благодарностью: он стоил нам ночи, но сэкономил, возможно, месяцы разбирательств с чем-то гораздо более неприятным, чем красный CI. А какой у вас был самый запоминающийся случай, когда одна маленькая зависимость устроила большой переполох, и что вы поменяли в своём процессе после этого? Расскажите, у нас тут как раз тот тип форума, где чужие шишки берегут свои.
 
Назад
Вверх