Цепочка поставок: зависимости и lock-файлы
Большая часть вашего приложения — это код, который написали не вы.
Цепочка поставок ПО (software supply chain) — все внешние компоненты, на которых держится приложение: библиотеки, их зависимости, инструменты сборки. SCA — анализ состава ПО на известные уязвимости.
Почему зависимости — это риск
Современный сервис тянет сотни транзитивных зависимостей. Каждая — чужой код, исполняющийся с вашими правами. Уязвимость в популярной библиотеке автоматически становится вашей уязвимостью, даже если ваш собственный код безупречен. Несколько громких инцидентов последних лет — именно из-за дыр в широко используемых пакетах.
Стоит осознать масштаб несоответствия. Команда тщательно ревьюит каждую строчку своего кода, но этот код — нередко лишь несколько процентов от того, что в итоге исполняется в продакшене. Остальное — пакеты, которые никто из команды построчно не читал, и пакеты этих пакетов, о существовании которых вы можете и не знать. Получается, что львиная доля вашей реальной поверхности атаки находится в коде, который вы не писали и не проверяли. Это не повод от него отказываться — переписывать всё самому и дороже, и опаснее, — но повод относиться к зависимостям как к полноценной части системы, а не как к «просто библиотекам».
Полезно различать два разных риска, которые часто смешивают. Первый — известные уязвимости в честных, добросовестных библиотеках: кто-то нашёл и опубликовал дефект, вышло исправление, а вы ещё не обновились. Второй — вредоносный код, намеренно внедрённый в пакет (через захват аккаунта мейнтейнера или пакет-двойник). Это разные угрозы с разными мерами защиты: от первого спасают сканеры и обновления, от второго — осторожность при добавлении пакетов и фиксация версий. Зрелая практика учитывает оба.
Известные уязвимости и SCA
Большинство проблем с зависимостями — не «новый взлом», а уже известные уязвимости (CVE), для которых давно есть исправление, но проект не обновился. SCA-инструменты (audit, Dependabot, OWASP Dependency-Check, Snyk) сверяют ваш список зависимостей с базами уязвимостей и сообщают, что и до какой версии обновить.
# Базовая проверка зависимостей на известные уязвимости
npm audit # Node
pip-audit # Python
# в CI: блокировать сборку при уязвимостях высокой критичности
Ключ — встроить SCA в CI и реагировать регулярно, а не раз в год: окно между публикацией уязвимости и её эксплуатацией коротко. Публикация CVE — это сигнал не только для защитников, но и для атакующих: как только дефект и способ его эксплуатации становятся общеизвестны, по интернету начинают массово сканировать уязвимые версии. Поэтому ценность измеряется не фактом «у нас есть сканер», а скоростью реакции: насколько быстро после появления исправления оно доезжает до прода.
Чтобы SCA приносил пользу, а не превращался в фоновый шум, важны две вещи. Во-первых, результат должен влиять на сборку: критичные уязвимости разумно делать блокирующими, иначе предупреждения копятся и их перестают замечать. Во-вторых, нужен механизм приоритизации и осознанного откладывания: не каждая найденная уязвимость реально достижима в вашем сценарии использования, и команде нужен внятный процесс, чтобы отличать «чинить сейчас» от «принять риск и зафиксировать решение», а не молча игнорировать весь отчёт.
Lock-файлы: воспроизводимость и целостность
Lock-файл (package-lock.json, poetry.lock и т.п.) фиксирует точные версии всех зависимостей, включая транзитивные, вместе с их контрольными суммами. Это даёт два эффекта безопасности: воспроизводимость (у всех и в проде ставится ровно то, что проверено) и целостность (хеш не даст незаметно подменить содержимое пакета).
// Уязвимо: плавающие версии — при каждой установке прилетает «что-то новое»
"dependencies": { "lib": "^1.0.0" } // подтянет 1.x, включая свежее непроверенное
// Безопаснее: lock-файл фиксирует точные версии + хеши,
// установка в CI/проде идёт строго по нему (например, npm ci)
Риски supply chain и минимизация
- Typosquatting. Пакет с именем, похожим на популярный, — проверяйте, что устанавливаете.
- Захват пакета. Скомпрометированный мейнтейнер выпускает вредоносную версию — отсюда ценность lock-файла и осторожных обновлений.
- Лишние зависимости. Каждая библиотека — поверхность атаки; не тяните пакет ради одной функции.
Минимизация поверхности применима и здесь: меньше зависимостей — меньше риска. Перед добавлением пакета оцените, активен ли он, много ли тянет за собой и нельзя ли обойтись стандартной библиотекой.
Как работает под капотом: дерево зависимостей и pinning
Менеджер пакетов строит дерево: ваши прямые зависимости тянут свои, те — свои. Реальная уязвимость часто сидит в транзитивной зависимости, о которой вы не знали. Lock-файл «прибивает» (pin) всё дерево к конкретным версиям и хешам; обновление становится осознанным шагом с возможностью прогнать тесты и SCA, а не случайностью при очередной установке.
Особенно поучительна роль хешей в lock-файле. Зафиксировать номер версии мало: в редких, но крайне опасных случаях содержимое уже опубликованного пакета может оказаться подменено. Контрольная сумма закрывает это: при установке менеджер сверяет хеш скачанного пакета с записанным в lock-файле, и при несовпадении установка падает, а не тихо подтягивает изменённый код. Так lock-файл из инструмента «чтобы у всех совпадали версии» превращается ещё и в контроль целостности цепочки поставки.
Прямые зависимости (вы их выбрали)
|-- lib-a 1.4.2
| |-- util-x 0.9.1 <- транзитивная, о ней вы не думали
| \-- util-y 2.0.0
\-- lib-b 3.1.0
\-- util-x 0.9.1 (та же, переиспользована)
Lock-файл фиксирует ВСЁ дерево: версии + хеши.
Здесь же лежит и ответ на типичный вопрос «фиксировать версии или всегда брать свежее?». Свежее автоматически звучит безопаснее, но «плавающие» диапазоны означают, что в любой момент в прод может приехать версия, которую вы не тестировали и не сканировали, — в том числе сразу после компрометации пакета. Зафиксированное дерево плюс дисциплина регулярных, но осознанных обновлений даёт лучшее из двух миров: вы не отстаёте от исправлений, но каждое обновление проходит через тесты и SCA, а не просачивается незаметно.
Частые ошибки
- Не обновлять зависимости. Большинство дыр — известные и уже исправленные выше.
- Игнорировать lock-файл / не коммитить его. Теряются воспроизводимость и контроль целостности.
- Тянуть лишние пакеты. Раздувают поверхность атаки.
- SCA «когда-нибудь». Проверки нужны в CI и регулярно.
Итоги
- Зависимости — чужой код с вашими правами и часть поверхности атаки.
- Большинство уязвимостей зависимостей известны: используйте SCA в CI и обновляйтесь.
- Lock-файлы дают воспроизводимость и целостность; коммитьте и уважайте их.
- Минимизируйте число зависимостей и проверяйте, что устанавливаете.