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

Без формализованного контекста такие выводы превращаются в исключения: CVE скрывают в конфигурации сканера, добавляют комментарий в тикет или помечают как допустимый риск на неопределённый срок. Исключение переживает пересборку контейнера, изменение зависимости и даже исчезновение первоначального обоснования. VEX решает другую задачу: передаёт машиночитаемое утверждение о статусе уязвимости для определённого продукта или компонента.
OpenVEX и CycloneDX VEX позволяют выразить этот контекст, но используют разные модели. Выбор между ними определяется не количеством полей, а тем, насколько надёжно документ связывается с контейнером и как его интерпретируют сканеры, шлюзы политик и платформы управления уязвимостями.
Чем VEX отличается от списка исключений
Обычное правило игнорирования чаще всего сопоставляет идентификатор уязвимости с именем пакета, путём или проектом. Оно сообщает инструменту, что результат не нужно показывать, но не объясняет состояние уязвимости как часть жизненного цикла продукта.
Полноценное VEX-утверждение связывает как минимум четыре элемента:
- идентификатор уязвимости, например CVE;
- конкретный продукт, образ, компонент или диапазон версий;
- результат анализа влияния;
- момент, когда этот результат считался актуальным.
Поэтому VEX не следует рассматривать как универсальный способ подавить предупреждение. Статус not_affected означает, что команда установила отсутствие влияния в заданном контексте. Он не означает «исправим позднее», «принимаем риск», «уязвимость имеет низкий приоритет» или «сканер слишком часто её показывает».
Если уязвимость можно эксплуатировать, но организация решила не устанавливать исправление, её статус не становится
not_affected. Решение о реакции и технический вывод о влиянии должны храниться раздельно.
Такое разделение устраняет бесконечные исключения. Документ описывает состояние определённого артефакта, а не бессрочное разрешение игнорировать CVE во всех будущих сборках.
Главное различие моделей
OpenVEX — компактный формат, построенный вокруг отдельных утверждений. Каждое из них соединяет уязвимость, один или несколько продуктов и статус. Документ можно выпускать независимо от SBOM и обновлять по мере расследования.
В CycloneDX VEX является сценарием использования общей модели BOM. Сведения об уязвимости находятся рядом с компонентами, версиями, оценками, анализом влияния и реакцией поставщика. Это удобно, если CycloneDX уже служит основным форматом обмена данными о составе и безопасности продукта. Для выбора формата SBOM в процессах CI и аудита полезна отдельная статья о SPDX 3.0 и CycloneDX 1.7.
| Критерий | OpenVEX | CycloneDX VEX |
|---|---|---|
| Основная единица | Утверждение: продукт, уязвимость, статус | Запись уязвимости с affects и analysis внутри BOM |
| Положительный статус | affected | exploitable в анализе и affected для версии |
| Отсутствие влияния | not_affected | not_affected в анализе и unaffected для версии |
| Исправление | fixed | resolved или resolved_with_pedigree |
| Расследование | under_investigation | in_triage |
| Привязка | Идентификаторы продуктов и вложенные subcomponents | Ссылка affects.ref на bom-ref компонента или сервиса |
| Сильная сторона | Компактный независимый документ | Богатый контекст в единой модели CycloneDX |
| Основной риск | Недостаточно точный идентификатор продукта | Смешение статуса версии, анализа влияния и реакции |
Как OpenVEX передаёт affected и not affected
OpenVEX определяет четыре статуса:
affected— для продукта рекомендуются действия по устранению или снижению воздействия;not_affected— уязвимость не влияет на указанный продукт, поэтому исправление именно по этой причине не требуется;fixed— указанная версия продукта содержит исправление;under_investigation— влияние пока устанавливается.
Статус относится к пересечению уязвимости и перечисленных продуктов. Это важное ограничение области действия: утверждение о пакете внутри одного контейнера нельзя автоматически распространять на любой образ с пакетом того же имени.
Для not_affected OpenVEX требует передать стандартизированное обоснование либо текстовое описание отсутствия влияния. Машиночитаемое обоснование предпочтительнее: сканер или policy engine может обработать его без анализа свободного текста. Текстовое поле полезно как дополнительное доказательство, но не как единственная основа автоматизации.
Каталог OpenVEX включает пять обоснований:
component_not_present— компонент не входит в продукт;vulnerable_code_not_present— компонент может присутствовать, но уязвимый код исключён;vulnerable_code_not_in_execute_path— уязвимый код не вызывается продуктом;vulnerable_code_cannot_be_controlled_by_adversary— атакующий не может управлять данными или условиями, необходимыми для эксплуатации;inline_mitigations_already_exist— встроенные меры полностью предотвращают известные способы эксплуатации.
Эти значения отличаются по силе доказательства. Отсутствие компонента можно проверить по составу артефакта. Недостижимость кода требует анализа путей исполнения. Невозможность управления со стороны атакующего и достаточность встроенных защит обычно зависят от архитектуры и модели угроз. Чем сильнее вывод зависит от окружения, тем важнее дополнять метку описанием проведённой проверки и ограничениями вывода.
Для статуса affected предусмотрено action_statement. В нём можно передать сведения об обновлении, временном обходном решении или другой реакции. Это позволяет не создавать отдельное исключение на период между обнаружением проблемы и выпуском исправленного образа.
Почему в CycloneDX одного поля статуса недостаточно
В CycloneDX есть два близких, но не взаимозаменяемых слоя состояния. Массив affects определяет, к какому компоненту или сервису относится уязвимость. Для конкретной версии или диапазона версий состояние affected, unaffected либо unknown указывается в поле affects[].versions[].status.
Объект analysis передаёт результат ручного или автоматизированного анализа конкретного проявления уязвимости. Его поле state поддерживает значения:
exploitable— уязвимость может быть прямо или косвенно эксплуатируема;not_affected— компонент или сервис не затронут;in_triage— выполняется расследование;resolved— проблема устранена;resolved_with_pedigree— устранение сопровождается сведениями о внесённых изменениях;false_positive— уязвимость ошибочно сопоставлена с компонентом или сервисом.
Таким образом, OpenVEX affected нельзя механически копировать в analysis.state: соответствующим состоянием анализа будет exploitable. Слово affected в CycloneDX используется на уровне версии в affects[].versions[].status. Если интеграция проверяет только одно из этих мест, она может потерять часть смысла.
Отдельного внимания требует false_positive. Этот статус означает ошибочное сопоставление уязвимости с объектом, тогда как not_affected допускает, что компонент распознан правильно, но эксплуатация невозможна в данном продукте. Объединение этих случаев затрудняет улучшение правил обнаружения: команда перестаёт понимать, где ошибся сканер, а где сработал продуктовый контекст.
CycloneDX также отделяет состояние от реакции. Поле response может сообщать, что исправление невозможно, не планируется, будет выполнено обновление или откат либо доступно обходное решение. Поэтому will_not_fix не должно превращаться в not_affected: первое описывает решение владельца, второе — технический результат анализа влияния.
Различия в обоснованиях not affected
CycloneDX использует более детализированный набор значений analysis.justification:
code_not_present;code_not_reachable;requires_configuration;requires_dependency;requires_environment;protected_by_compiler;protected_at_runtime;protected_at_perimeter;protected_by_mitigating_control.
Для некоторых значений соответствие OpenVEX очевидно. vulnerable_code_not_present близко к code_not_present, а vulnerable_code_not_in_execute_path — к code_not_reachable. Но полного взаимно однозначного отображения нет.
Например, OpenVEX inline_mitigations_already_exist при переносе в CycloneDX может означать защиту компилятором, средой исполнения, периметром или отдельным компенсирующим контролем. Выбор зависит от реального механизма защиты. В обратную сторону несколько разных обоснований CycloneDX могут схлопнуться в одну метку OpenVEX.
component_not_present также нельзя бездумно заменить на code_not_present. В первом случае отсутствует сам компонент, во втором речь идёт об уязвимом коде. Если контейнерный сканер обнаружил пакет, а VEX утверждает component_not_present, сначала нужно проверить идентификаторы и область действия документа. Возможно, сканер нашёл другой экземпляр пакета, VEX относится к иной архитектуре либо документ привязан не к той сборке.
При конвертации между форматами следует сохранять исходное пояснение в текстовом поле impact_statement или analysis.detail. Оно не заменяет перечислимую метку, но предотвращает потерю доказательств там, где каталоги обоснований различаются.
Привязка VEX к контейнеру и компоненту
Большинство проблем с VEX возникает не из-за статуса, а из-за неверного сопоставления объекта. Утверждение может быть логически правильным, но бесполезным для сканера, если тот не способен связать его с анализируемым образом и найденным пакетом.
Идентификаторы в OpenVEX
OpenVEX позволяет описывать продукт с помощью идентификатора @id, Package URL, CPE, хешей и вложенных subcomponents. Для контейнерного сценария полезно различать два уровня:
- продукт — конкретный контейнерный образ;
- subcomponent — пакет или библиотека внутри него, с которой связана уязвимость.
Наиболее устойчивая область действия строится вокруг digest образа и точного PURL компонента. Тег реестра удобен для человека, но изменяем: сегодня service:stable может указывать на одну сборку, а после публикации — на другую. Если VEX продолжит сопоставляться только по тегу, старое решение not_affected рискует распространиться на новый артефакт.
Привязка только к PURL пакета тоже может быть слишком широкой. Одна и та же библиотека в двух образах способна иметь разный путь исполнения, конфигурацию и внешнюю доступность. Поэтому продуктовый контекст нельзя отбрасывать, если обоснование зависит от архитектуры приложения.

Ссылки в CycloneDX
В CycloneDX поле affects.ref ссылается на bom-ref компонента или сервиса, объявленного в BOM. Эта модель удобна тем, что связь не приходится восстанавливать только по имени и версии: запись уязвимости адресует конкретный объект документа.
Но bom-ref должен быть стабильным и однозначным в рамках процесса. Если генератор при каждом запуске назначает случайные ссылки, а VEX выпускается отдельно от исходного BOM, объединение документов усложняется. Команде необходимо заранее определить, какие идентификаторы сохраняются между генерацией, анализом и публикацией.
Диапазоны версий в affects полезны для продуктовых линеек, но для контейнеров ими нельзя подменять digest. Версия приложения может остаться прежней после пересборки базового слоя или зависимости. Если результат анализа относится к точному составу образа, идентичность артефакта должна быть частью области действия.
Как сканеры применяют VEX
Поддержка формата на сайте или в справке инструмента ещё не гарантирует нужного поведения. Сканер может читать CycloneDX как SBOM, выводить отчёт в CycloneDX и при этом не применять секцию VEX как основание для изменения результатов. Аналогично, импорт документа не всегда означает, что affected добавит отсутствующую находку, а not_affected сохранится в аудиторском отчёте.
Grype служит показательным примером явной модели обработки. Он принимает OpenVEX и CSAF VEX как входные документы. Статусы not_affected и fixed используются для фильтрации результатов. Записи не обязательно исчезают бесследно: в табличном представлении подавленные результаты можно показать отдельно, а в JSON они помещаются в массив проигнорированных совпадений с применёнными правилами.
affected и under_investigation решают обратную задачу — дополняют результаты сведениями VEX. Такое поведение требуется включать явно. Это разумное различие: подавление уже найденной CVE и добавление утверждения, которого не было в исходном результате сканирования, имеют разный уровень риска.
При этом заявленная поддержка CycloneDX как формата SBOM не должна автоматически трактоваться как поддержка CycloneDX VEX на входе. В документации Grype среди входных VEX-форматов перечислены OpenVEX и CSAF VEX. Следовательно, команда, выбравшая CycloneDX VEX, должна проверить собственный маршрут доставки: возможно, документ обработает центральная платформа, но не локальный CLI-сканер.
Подход Claircore к приёмочным тестам показывает, как проверять такие интеграции воспроизводимо. Тестовый набор объединяет контейнерный образ, VEX-документы и ожидаемые результаты. Анализатор индексирует образ, загружает приложенные документы, сопоставляет уязвимости с пакетами и сравнивает результат с эталоном. В данном механизме используются документы CSAF VEX, поэтому его нельзя считать подтверждением поддержки OpenVEX или CycloneDX VEX. Важен сам принцип: поведение анализатора следует фиксировать тестами, а не предполагать по названию поддерживаемого стандарта.
Сканирование контейнеров и проверку обработки VEX можно выполнять на существующем CI-раннере, локальном сервере, в облачном CI или в отдельном изолированном контуре. Если команде требуется самостоятельно управляемое окружение для таких задач, одним из вариантов размещения может быть VPS-сервер, но он не является обязательным условием.
Что проверить до выбора формата
Для рабочего процесса vulnerability management важна сквозная цепочка, а не только валидность JSON. Минимальный набор проверок включает следующие сценарии:
- Точное совпадение продукта. Документ для одного digest не должен применяться к другому образу с тем же тегом.
- Совпадение компонента. Утверждение для одного PURL не должно подавлять одноимённый пакет другой экосистемы или версии.
- Отображение статусов. Нужно отдельно проверить
affected,not_affected, исправленное состояние и расследование. - Сохранение причины. Обоснование и текстовые детали должны оставаться доступными в API или отчёте после фильтрации.
- Конфликт документов. Более новое утверждение должно предсказуемо заменять старое либо вызывать контролируемый конфликт.
- Наблюдаемость подавления. Аудитор должен видеть, какая находка скрыта, каким документом и на каком основании.
- Пересборка образа. Старый
not_affectedне должен автоматически переходить на новый digest без повторной оценки.
Полезно включить отрицательные тесты: неверный digest, отсутствующий bom-ref, несовпадающий PURL, неизвестное обоснование и два документа с разными статусами. Именно такие случаи выявляют опасное поведение «не удалось сопоставить, поэтому проигнорировали» или «последний загруженный файл всегда побеждает».
Как не превратить VEX в новый список исключений
Формат сам по себе не обеспечивает качество решения. Если разрешить выпуск not_affected без владельца, доказательств и области действия, VEX станет более сложной оболочкой для прежних исключений.
Устойчивый процесс опирается на несколько правил:
- Один источник решения. Статус формируется в системе, где хранится результат анализа, а не редактируется независимо в нескольких файлах.
- Неизменяемая область действия. Для контейнера предпочтителен digest; тег используется только как дополнительная понятная метка.
- Отдельный компонентный контекст. Указываются PURL, версия и при необходимости архитектура или вариант сборки.
- Проверяемое обоснование. Машиночитаемая причина сопровождается кратким описанием доказательства и ограничений.
- Переоценка по событию. Новый digest, обновление компонента, изменение конфигурации или появление новых данных об эксплуатации запускают повторный анализ.
- История состояний. Переходы от расследования к affected, not affected или fixed не стираются из аудиторского следа.
- Раздельные представления. Подавленная находка не мешает разработчику, но остаётся доступной security-команде и аудитору.
Необязательно назначать каждому VEX формальный срок истечения, но бессрочный вывод должен быть обоснован природой доказательства. Отсутствие компонента в неизменяемом образе остаётся проверяемым свойством этого digest. Вывод о защите сетевым периметром может потерять силу после изменения маршрутизации или политики доступа, поэтому требует событийной либо периодической переоценки.
Когда выбирать OpenVEX
OpenVEX подходит, если нужен компактный сопровождающий документ, который выпускается отдельно от SBOM и передаёт прежде всего продуктовый статус CVE. Это практичный выбор для конвейеров, где локальный сканер прямо поддерживает OpenVEX, а команде важны простое версионирование, автоматическое подавление not_affected и понятные переходы между четырьмя состояниями.
Формат особенно удобен, когда один производитель контейнера публикует оценки для downstream-потребителей, не заставляя их заменять собственный формат SBOM. OpenVEX также проще использовать как независимую аттестацию, приложенную к конкретному образу.
Его ограничением становится меньшая выразительность каталога обоснований и необходимость отдельно поддерживать связь с данными о компонентах, версиях и оценках риска. Если идентификаторы продуктов нестабильны, компактность документа не спасёт от ошибок сопоставления.
Когда выбирать CycloneDX VEX
CycloneDX VEX логичен для организации, где CycloneDX уже является общей моделью данных, а компоненты имеют устойчивые bom-ref. Он позволяет хранить рядом область влияния, версии, состояние анализа, обоснование, реакцию, временные метки и подробности оценки.
Этот вариант полезен платформам, которым нужно различать ошибочное обнаружение, отсутствие влияния, эксплуатируемость, завершённое исправление и исправление с подтверждаемой историей изменений. Более детальные обоснования помогают строить политики, учитывающие источник защиты: компилятор, runtime, периметр, конфигурацию или компенсирующий контроль.
Главное условие — реальная поддержка всей цепочкой инструментов. Если центральная платформа понимает CycloneDX VEX, а сканер в CI читает CycloneDX только как перечень компонентов, команде потребуется преобразование формата или применение VEX на другом этапе.
Можно ли использовать оба формата
Совместное использование оправдано, если организация взаимодействует с разными потребителями. Например, внутренняя платформа может хранить расширенную модель анализа, выпускать CycloneDX VEX для систем, работающих с BOM, и OpenVEX для сканеров, которые принимают его непосредственно.
Но два документа не должны редактироваться как независимые источники истины. Иначе одна система получит not_affected, а другая продолжит видеть affected. Безопаснее хранить каноническое решение во внутренней модели и генерировать оба представления с контролируемым отображением статусов и обоснований.
При преобразовании необходимо регистрировать потери смысла. Например, несколько обоснований CycloneDX могут перейти в одну метку OpenVEX, а OpenVEX affected потребуется разложить на affects[].versions[].status=affected и analysis.state=exploitable. Если точного соответствия нет, конвертер не должен молча выбирать удобное значение: следует сохранить подробность в текстовом поле или отправить запись на проверку.
Итоговый критерий выбора
OpenVEX выигрывает как лёгкий отдельный канал передачи статуса. CycloneDX VEX выигрывает как часть более широкой и детальной модели продукта. Однако для управления контейнерными уязвимостями решающим является не название формата, а качество привязки и фактическое поведение потребителя.
Если сканер надёжно сопоставляет digest образа, PURL компонента, CVE и актуальное утверждение OpenVEX, компактного формата достаточно. Если инфраструктура уже объединяет компоненты и уязвимости через стабильные bom-ref, а платформе нужны детальные причины и реакции, CycloneDX VEX даст больше контекста.
В обоих случаях VEX должен оставаться проверяемым утверждением, а не командой «скрыть предупреждение». Неизменяемая область действия, машиночитаемая причина, видимый аудиторский след и тесты обработки документов превращают разовые исключения в управляемый жизненный цикл статусов уязвимости.


