Привет, Хабр!

Меня зовут Артём Бердашкевич, в Positive Technologies руковожу направлением DevSecOps. Сегодня хочу поговорить о теме, которая с годами становится только острее — о контроле зависимостей и о том, почему привычных подходов к нему уже катастрофически не хватает. Современная разработка давно превратилась в сборку из готовых компонентов, где мы почти не пишем код с нуля, а комбинируем фреймворки, библиотеки и модули с открытым исходным кодом. Такой подход радикально ускоряет вывод продуктов на рынок, но за скорость приходится платить прозрачностью. Команда часто не знает точный состав своего приложения до финальной сборки. Почему это стало большой проблемой и что с ней делать — читайте в этой статье.

Содержание

Прозрачность, лицензии и порядок как три вечные боли разработки

Недостаток прозрачности в цепочке поставок кода — не новость. Команды годами сталкиваются с одними и теми же сложностями:

  1. Поиск потенциальных уязвимостей. В кодовой базе могут оказаться библиотеки с известными CVE, а команда об этом даже не подозревает, пока не случится инцидент.

  2. Поддержка лицензионной чистоты. Один случайно добавленный компонент с вирусной лицензией способен создать юридические риски для всего продукта.

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

Для ответа на эти вызовы индустрия создала отдельный класс инструментов — Software Composition Analysis (SCA), для анализа состава программного обеспечения. SCA сверяет версии библиотек с базами CVE, контролирует лицензии и помогает превратить хаос зависимостей в управляемый реестр.

Звучит как готовое решение, однако дьявол кроется в деталях. Чтобы в них разобраться, начнём с масштаба проблемы.

Восемь миллионов причин заняться контролем прямо сейчас

Реальную картину легко недооценить, пока не посмотришь на цифры. Для наглядности представим, что у нас есть некий вендор с портфелем из 25 продуктов. Каждый продукт существует в пятидесяти версиях, и в каждой версии лежит по 25 компонентов. Один компонент тянет за собой полсотни прямых зависимостей, а каждая из них обрастает еще пятью транзитивными — тех, что нужны для работы самих библиотек. Простая арифметика дает на выходе почти 8 миллионов сущностей, требующих контроля.

Теперь допустим, что в одной из популярных библиотек находят уязвимость с высоким рейтингом или так называемую закладку, то есть намеренно добавленный вредоносный код. Перед командой встает задача: найти все затронутые продукты и версии, определить, как уязвимый код туда попал, и понять, что конкретно обновлять. Без выстроенной системы SCA в этот момент начинается хаос. Разработчики вручную перебирают старые коммиты в GitLab, пытаясь восстановить картину, а релизный цикл встает намертво. На поиск уходят дни или даже недели, и ясности это, как правило, не прибавляет.

Именно поэтому SCA давно перерос скромную роль утилиты для отдела информационной безопасности. Сегодня это фундамент для управляемой и прогнозируемой разработки.

Зачем вам нужен SCA
Зачем вам нужен SCA

Внутренняя потребность в порядке со временем получила поддержку и снаружи: регуляторы через ГОСТ Р 56939, приказы ФСТЭК и федеральные законы о безопасности КИИ методично превращают SCA из полезной практики в обязательное требование для всех, чей код работает в чувствительных сегментах инфраструктуры. Однако одних формальных требований недостаточно, ведь чтобы понять, как подступиться к задаче на практике, полезно сначала оценить уровень своей зрелости. Как правило, компании проходят через три закономерных этапа, и на каждом меняется и инструментарий, и мышление команды.

Три стадии зрелости SCA: от ручного сканера к архитектуре

Внедрение контроля зависимостей почти никогда не происходит одномоментно. Компании проходят через закономерные этапы эволюции.

Стадия первая: ad-hoc сканирование

Самый простой и естественный способ начать — взять готовый сканер и запустить его локально. Инженер берет Trivy или аналогичный инструмент, прогоняет через него образ или репозиторий и получает артефакт с результатами, а дальше этот отчет улетает куда-то в почту или чат ответственным лицам. Метод полуручной, требует дисциплины и постоянного внимания, но для пилотных проектов и малого числа компонентов его вполне хватает. Проблемы начинаются с ростом количества сервисов и частоты билдов, когда ручной запуск становится узким горлышком.

Стадия вторая: конвейерное сканирование

Следующий шаг — встраивание сканера в CI/CD. На этом этапе выделяется отдельный стейдж, на котором каждый собранный Docker-контейнер или артефакт прогоняется через инструмент анализа зависимостей. Звучит прогрессивно, но на практике это часто превращается в гигантское кладбище файлов. Когда у вас полторы-две тысячи пайплайнов и к каждому прикреплен JSON с отчетом Trivy, разработчик физически не способен просматривать их все. В итоге большая часть команды просто игнорирует эти артефакты до момента, пока безопасники не придут с очередным аудитом.

Стадия третья: архитектура зрелого процесса

Компании, прошедшие первые два этапа, приходят к пониманию простой истины. SCA — это не утилита и не стейдж в пайплайне, а распределенная архитектура из нескольких связанных компонентов. Каждый из них решает свою четкую задачу и передает данные следующему.

С чего начинался и во что «вызреввет» контроль зависимостей
С чего начинался и во что «вызреввет» контроль зависимостей

Такая система позволяет не просто находить проблемы, а предотвращать их появление, расследовать инциденты и давать быстрые ответы на вопросы регуляторов и заказчиков.

Как устроен зрелый контроль зависимостей

В итоге хождения по всем этим стадиям из разрозненных инструментов у вас должна получиться единая архитектура: файрвол фильтрует входящее, SBOM фиксирует состав, обогащение добавляет контекст, древо связывает компоненты с продуктами, а security gate не пропускает сомнительный код в релиз. Из всего этого каркаса мы разберем два наиболее важных элемента: файрвол зависимостей и процесс работы со SBOM.

Файрвол зависимостей и шесть эшелонов проверки

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

Какие проверки имеет смысл встроить в этот конвейер? Набор растет по мере зрелости процесса, но минимально жизнеспособная конфигурация выглядит так.

  • Первый и самый очевидный барьер — проверка на известные уязвимости. Движок выдергивает наименование пакета с версией и сверяет их с базой CVE.

  • Второй эшелон — динамический анализ в песочнице. Пакет или образ разворачивается на виртуальной машине, где его проверяют антивирус и поведенческие анализаторы.

  • Третьей стадией всё чаще становится PoliticSearcher — рекурсивный анализ пакетов и архивов на упоминание политических лозунгов. Логика проста: если внутри библиотеки обнаруживается какая-то политическая агитация, то с высокой долей вероятности там есть и вредоносная закладка.

  • Четвертая проверка — статический анализ всех зависимых компонентов. Большинству команд этот этап не нужен, но разработчики средств защиты информации обязаны прогонять через SAST вообще все компоненты продукта, включая ту же PostgreSQL в его составе. Требование жесткое и тянет за собой отдельный кластер для перемалывания исходников.

  • Пятый пункт касается лицензионной чистоты. При выходе на международный рынок сильно растет риск судебных исков за нарушение лицензионных условий. Поэтому важно предотвратить попадание в продукт библиотек с сомнительной лицензией еще на этапе модерируемого репозитория.

  • Шестой, замыкающий эшелон — проверка с использованием ML-моделей. Машинное обучение работает как третий уровень поиска закладок после «песка» и PoliticSearcher.

SBOM и его эволюция: от JSON-чика к системе знаний

Допустим, файрвол настроен и отлично справляется, а значит на входе у нас всё чисто. Но что происходит с компонентами дальше, когда они уже внутри продукта? Здесь на сцену выходит Software Bill of Materials (SBOM) — документ, описывающий, из каких компонентов и зависимостей состоит ваше приложение.

Изначально SBOM задумывался как универсальный формат для структурированного списка зависимостей. Независимо от того, где лежит ваш компонент — в Docker-образе, в requirements.txt для Python или в package-lock.json, — SBOM приводит всё к единому JSON с понятным синтаксисом и одинаковыми полями. Идея проста: все инструменты обмениваются информацией на одном языке.

Довольно быстро рынок осознал, что потенциал формата гораздо шире. SBOM можно передать регулятору, чтобы тот проверил состав продукта без доступа к исходникам. Также его можно переиспользовать внутри компании, чтобы не сканировать одни и те же компоненты разными инструментами по второму кругу. А еще он превратился в стандарт взаимодействия между участниками цепочки поставок — по сути, универсальный язык для обмена данными о зависимостях.

Дальше появились дополнительные требования:

  1. Самое простое — поиск известных уязвимостей. Разные движки с разными базами CVE решают эту задачу примерно одинаково: берут список компонентов из SBOM и подсвечивают проблемные версии.

  2. Второй слой — обогащение метаинформацией. Хороший SBOM отвечает не только на вопрос «что внутри», но и на вопросы «откуда это взялось» и «кто это собрал».

  3. Третья функция — external references. Регулятору важно понимать, собираете ли вы компонент сами или берёте готовый бинарник со стороны. Так, без них «форкнутый» и оригинальный OpenSSL выглядят одинаково, хотя уровень доверия к ним принципиально разный.

  4. Четвёртый механизм — разделение на «свой» и «чужой». Когда в продукте полторы сотни зависимостей, без специальной пометки непонятно, что разработано внутри компании, а что пришло извне. Регулятор требует статического анализа всего собранного вами, и без этого разделения вы даже не сформируете корректный план проверки.

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

Так выглядит наш собственный инструмент работы со SBOM — здесь видно древо и различные компоненты с метаинформацией о сборке
Так выглядит наш собственный инструмент работы со SBOM — здесь видно древо и различные компоненты с метаинформацией о сборке

Все пять элементов в совокупности создают систему, в которой SBOM играет роль «живой» базы знаний о продукте. Дальше посмотрим, как эта теория сталкивается с реальностью технологического стека, где кроме Python и Go есть бинарные файлы, архивы и Docker-образы со своими особенностями.

Особенности внедрения за пределами Python, Go и Java

С современными языками — Python, Go, JavaScript — всё работает предсказуемо. Пакетные менеджеры и знакомые всем файлы дают полную картину зависимостей, сборка SBOM автоматизирована и прогон через файрвол для таких проектов не вызывает вопросов. Однако за пределами этого стека встречаются сценарии посложнее.

Бинарные файлы и готовые архивы

Механизм модерируемого репозитория заточен под пакетные менеджеры, а здесь привычные подходы буксуют. Как прогонять через карантин собранный бинарь? По какому принципу разделять зависимости внутри архива с десятками компонентов? И нужно ли вообще разбирать его на части или можно проверять монолитным блоком? Однозначного ответа индустрия пока не дала, и каждая команда изобретает свой подход.

Docker-образы и проблема External Refs

Ещё интереснее обстоят дела с Docker-образами. Допустим, мы прогоняем образ через файрвол и обнаруживаем внутри него Bash. Теперь нужно решить, что именно указывать в SBOM в качестве источника: ссылку на репозиторий самого образа, исходный код Bash или конкретный бинарный файл. Этот выбор во многом определяет, насколько прозрачной окажется цепочка поставок в дальнейшем.

Любопытно, что сама развилка здесь не столько техническая, сколько методологическая. Если вы хотите контролировать целостность контейнера в сборе, то привязываете external ref к репозиторию образа. Если важнее отслеживать уязвимости в компонентах независимо от упаковки, то спускаетесь на уровень исходников или бинарных файлов внутри контейнера.

Компромисс вместо идеала

Особенности бинарных файлов и Docker-образов подводят к простой мысли: гнаться за стопроцентным охватом всего технологического стека одной идеальной схемой — значит тратить ресурсы впустую. Практика показывает, что гораздо полезнее честно определить границы того, что вы реально покрываете, и явно зафиксировать исключения. В любой серьёзной кодовой базе найдутся самописные скрипты, которые качают бинари по маске из внутреннего репозитория и о которых не узнает ни один стандартный сканер вроде Trivy. Команда должна знать про такие случаи отдельно и обрабатывать их собственным процессом.

Гонка за полной автоматизацией вообще отвлекает от куда более фундаментальных проблем.

Финальный босс: три нерешенные проблемы DevSecOps

После того как вы выстроили файрвол зависимостей, наладили сборку SBOM и обогатили артефакты метаинформацией, наступает отрезвляющий момент. Выясняется, что часть вопросов по-прежнему не имеет устоявшихся ответов даже у лидеров индустрии. Эти проблемы стоит обсуждать открыто, потому что замалчивание создает ложное ощущение, будто вы одни с ними столкнулись. Мы выделили три ключевых вызова.

Проклятие легаси

Первая и самая болезненная проблема — legacy. В любом зрелом продукте есть компоненты, бинарные файлы и Docker-образы, возникшие задолго до внедрения SCA. Файрвол настроен, SBOM для нового кода собирается исправно, но старый пласт остаётся непокрытым, а разделить «свой» и «чужой» для кода без внешних ссылок и метаинформации почти невозможно. Универсального подхода рынок пока не предложил, и каждая команда изобретает свою архитектуру.

Те самые галочки в нашем инструменте
Те самые галочки в нашем инструменте

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

Функциональные ограничения

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

  • Первый вопрос — пропускная способность. Разработчик хочет обновить полторы сотни мелких JavaScript-пакетов за раз, но способен ли файрвол переварить такой объём без деградации, заранее неизвестно.

  • Второй касается допустимого времени проверки. Идеалистический подход «тратить столько, сколько нужно» разбивается о реальность: если одна зависимость проверяется сутки, разработка просто встаёт.

  • Третий — потребление ресурсов. «Песок», гоняющий все компоненты подряд, способен съесть ощутимую долю мощностей ЦОДа, и в какой момент затраты перестают быть оправданными, каждая команда вычисляет самостоятельно.

Транзитивные зависимости: архитектурная дилемма

Третья проблема носит почти философский характер. Разработчики привычно тянут верхнеуровневую зависимость, не интересуясь её содержимым. Файрвол проверяет фреймворк по всем критериям — CVE, лицензии, песочница, PoliticSearcher — и пропускает его. Однако внутри фреймворка сидит транзитивная зависимость, которая ни один из этих критериев не проходит. Что делать?

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

Что из этого следует

Контроль зависимостей начинается с осознания масштаба проблемы и при системном подходе вырастает в полноценную архитектуру из файрвола, SBOM и эшелонов проверок. В статье мы рассмотрели, как работают эти механизмы, с какими особенностями сталкиваются команды при работе с бинарными файлами и Docker-образами, и какие вопросы пока остаются нерешёнными.

Из всего этого складывается простая картина: SCA — не продукт, который можно один раз внедрить и забыть, а живой процесс. Он требует настройки под конкретный технологический стек, регулярного пересмотра политик и внимания к деталям, которые не описаны ни в одном ГОСТе. При этом даже самые продвинутые команды продолжают искать ответы на часть вопросов — и это нормально, потому что индустрия всё ещё в движении.

Все три проблемы имеют кое-что общее — они не решаются одним инструментом или очередной версией сканера. Это архитектурные и методологические вызовы, требующие совместных усилий всего профессионального сообщества. Поэтому если вы уже сталкивались с ними в своей работе и нашли какое-то решение — рассказывайте в комментариях!