Практический разбор по мотивам доклада Алексея Билая и Ильи Сафронова
У vulnerability management есть неприятная особенность: сама по себе покупка или установка сканеров почти никогда не решает проблему управления уязвимостями. Наоборот, часто именно после подключения сканеров начинается настоящая боль. SAST приносит одни findings, DAST другие, SCA третьи, infrastructure/network-сканеры четвёртые. У каждого инструмента свои форматы, своя логика severity, свои поля, свои идентификаторы, свои false positive и свой способ объяснять, что именно он нашёл.
В какой-то момент команда перестаёт управлять уязвимостями и начинает управлять шумом. Люди сидят на потоке, открывают однотипные findings, сравнивают результаты разных сканеров, руками закрывают false positive, руками подтверждают очевидные случаи и пытаются понять, что из очередных десятков тысяч сработок действительно требует внимания.
Доклад Алексея Билая и Ильи Сафронова как раз про этот момент: когда готовое vulnerability management-решение оказалось неудобным, команда написала собственный «велосипед» вместо DefectDojo и встроила в него автоматический triage, deduplication, cross-scanner deduplication, историю находок и LLM triage с sandbox. Главное в этой истории не сам факт самописной системы, а инженерная рамка: как превратить разрозненные findings в управляемый поток, где человек разбирает не все подряд, а только то, в чем автоматика не уверена.
Проблема: много сканеров, много форматов, много шума
Исходная ситуация знакома почти любой зрелой AppSec-команде. В компании есть не один сканер, а целый набор инструментов:
- сканеры исходного кода и SAST;
- DAST и проверки приложений снаружи;
- SCA и анализ зависимостей, если он есть в контуре;
- infrastructure/network-сканеры;
- сканеры периметра;
- проверки секретов, контейнеров, образов, сервисов и внутренних targets.
Каждый сканер генерирует собственный поток findings. Даже когда два инструмента говорят об одной и той же проблеме, они часто называют её по-разному, по-разному оценивают severity, используют разные evidence и разные идентификаторы. Один результат может выглядеть как critical, другой как high, третий вообще не совпасть по категории, хотя по сути речь идёт об одном asset и одном риске.
Отдельная проблема — ложные срабатывания. Scanners полезны, но они шумят. Если все результаты складывать в очередь ручной обработки без предварительной нормализации, deduplication и triage, человек быстро превращается в интерфейс к чужим JSON/XML/CSV-выгрузкам. Это дорогая и выматывающая работа: нужно постоянно принимать однотипные решения, помнить контекст активов, отслеживать повторные появления уязвимостей и при этом не пропустить настоящий high или critical.
Команде нужна единая точка входа: место, куда можно загружать findings из разных сканеров, приводить их к общему контракту, дедуплицировать, обогащать контекстом, вести жизненный цикл и timeline и принимать решения о дальнейшей обработке.
Почему DefectDojo не подошел
Логичный первый кандидат для такой задачи — DefectDojo. Это известный инструмент в области vulnerability management, и многие команды начинают именно с него: он умеет принимать результаты сканеров, хранить findings, показывать статусы, помогать с triage и отчётностью.
Но в этом кейсе команда уперлась в несколько ограничений.
Первое — производительность. Когда речь идёт не о сотнях, а о десятках тысяч findings, система должна спокойно переваривать большие объёмы данных, массовые загрузки и повторные прогоны сканеров. Если интерфейс и backend начинают тормозить, аналитик опять оказывается заложником инструмента.
Второе — deduplication. Для vulnerability management недостаточно просто склеивать абсолютно одинаковые записи. Нужна полноценная логика: понимать, что finding уже был, что он появился снова, что его нашёл другой сканер, что это тот же asset и та же проблема, а не новая независимая уязвимость. В докладе отдельно подчёркивалась cross-scanner deduplication: возможность объединять логически одинаковые findings, приехавшие из разных сканеров.
Третье — интеграции. Если появляется новый сканер, нестандартная внутренняя проверка или корпоративный источник данных, не хочется каждый раз писать тяжёлую отдельную интеграцию и разбираться, как именно DefectDojo ожидает увидеть этот формат. Команде нужен был более универсальный входной контракт.
И, конечно, в докладе прозвучал самый важный «минус»: тёмная тема за деньги. Шутка шуткой, но за ней есть практическая мысль: если аналитики каждый день живут в интерфейсе triage, удобство продукта становится рабочим фактором, а не косметикой.
Архитектура своего решения
Собственное решение построили без избыточной инфраструктурной сложности. В докладе система описана как backend на Go со стандартными библиотеками и Postgres в роли основного хранилища.
От отдельных очередей вроде RabbitMQ/ZeroMQ команда отказалась. Идея была в том, чтобы не добавлять ещё одно звено, которое нужно разворачивать, мониторить, обновлять, бэкапить и чинить. Если состояние, задачи обработки, findings, история и результаты лежат в одном управляемом слое хранения, эксплуатация становится проще. Для внутреннего vulnerability management-инструмента это важный компромисс: меньше движущихся частей, меньше эксплуатационных накладных расходов.
На вход система принимает результаты сканеров. Дальше данные проходят через модули обработки: нормализацию, deduplication, cross-scanner deduplication, обогащение, triage и обновление lifecycle. Отдельно есть integrations и notifications: система не должна быть изолированной витриной, ей нужно подтягивать контекст из смежных корпоративных источников и отправлять решения туда, где команда реально работает.
Архитектурно это похоже не на «большой портал про уязвимости», а на конвейер:
- принять finding из сканера;
- привести к единому формату;
- сопоставить с asset и контекстом;
- проверить, не является ли это повтором;
- обновить timeline;
- провести автоматический или LLM triage;
- принять решение: закрыть, подтвердить, отправить человеку, уведомить владельцев или передать дальше в integrations.
В такой схеме интерфейс важен, но он не является центром вселенной. Центр — это нормализованная модель данных и надёжный жизненный цикл и timeline finding.
SARIF как входной контракт
Ключевое решение — использовать SARIF как универсальный входной контракт.
SARIF, Static Analysis Results Interchange Format, формально родился вокруг результатов статического анализа. Но в докладе важна практическая интерпретация: структура SARIF достаточно богатая, чтобы в неё можно было уложить не только SAST, но и DAST, infrastructure/network-сканеры, проверки секретов и другие типы findings.
Это снимает часть боли с интеграциями. Вместо того чтобы для каждого сканера писать отдельный импортер с собственной моделью, можно договориться: всё, что приходит в систему, приводится к SARIF. Если сканер уже умеет отдавать SARIF, он подключается проще. Если не умеет, достаточно написать адаптер до общего формата, а не полноценную интеграцию в бизнес-логику vulnerability management.
Для AppSec-команды это сильная граница ответственности. Scanner отвечает за обнаружение и за корректное представление результата. Vulnerability management-система отвечает за обработку finding: deduplication, timeline, triage, lifecycle, notifications и integrations.
Важно и то, что SARIF не пытаются использовать как красивую оболочку «для галочки». В докладе отдельно звучало, что поддержка реализована полноценно по опубликованному стандарту. Это важно: если входной контракт частичный, каждая новая интеграция снова начинает пробивать дырки в общей модели.
Deduplication, cross-scanner deduplication и timeline
Deduplication в vulnerability management часто недооценивают. На маленьких объёмах кажется, что достаточно сравнить пару полей и скрыть дубликаты. На реальных объёмах так не работает.
Finding живёт во времени. Сегодня сканер нашёл проблему, завтра не нашёл, через неделю нашёл снова. Один сканер видит её как сетевую уязвимость, другой как misconfiguration, третий как слабое место в конкретном сервисе. Asset может переехать, сменить hostname, получить другой IP или появиться в другом источнике инвентаризации. Severity может отличаться от сканера к сканеру: critical/high/medium/low не всегда означают одно и то же без контекста.
Поэтому в системе нужна не только deduplication, но и finding lifecycle/timeline. Команда должна видеть:
- когда finding появился впервые;
- кто его нашёл и каким сканером;
- находился ли он повторно;
- какие доказательства/evidence были у разных сканеров;
- менялась ли severity;
- кто и когда принимал triage-решение;
- когда finding был закрыт, подтвержден или отправлен на ручной разбор;
- не всплыл ли он снова после закрытия.
Cross-scanner deduplication добавляет следующий уровень: если разные сканеры привезли одну и ту же логическую проблему, система должна не плодить независимые карточки, а связывать их в единый объект управления. Для аналитика это принципиально. Он работает не с количеством строк в выгрузках, а с реальными проблемами на assets.
LLM triage: где модель полезна
Самая заметная часть решения — LLM triage. Но его роль в докладе описана достаточно прагматично: модель не заменяет весь vulnerability management, а помогает разгрузить повторяющийся triage.
Есть два режима.
Первый — однораундовый triage. Он подходит для findings, где хватает статического контекста. Например, для SAST-результата можно взять данные сканера, фрагмент кода или описание проблемы, добавить системный prompt и попросить модель оценить, насколько finding похож на реальную уязвимость или false positive. Важно, что данные сканера считаются недоверенным input: они не становятся частью инструкций, а передаются как данные для анализа.
Второй — triage с инструментами через sandbox. Он нужен там, где для проверки finding требуется взаимодействие с живым asset: например, сетевой finding надо перепроверить через nmap, curl или другие заранее разрешённые tools. В этом случае LLM формирует план или команды в рамках разрешённого набора, sandbox выполняет их в ограниченной среде, возвращает output, а модель уже на втором шаге сопоставляет исходный finding с фактической проверкой.
По итогу LLM triage должен выдать не литературное объяснение, а операционный результат:
- finding подтвердился;
- finding похож на false positive;
- данных недостаточно;
- нужна ручная проверка;
- какая категория подходит для нормализации;
- насколько модель уверена в своём решении.
После этого система может автоматически закрыть очевидный false positive, подтвердить понятный случай или отправить finding на ручной triage.
Sandbox и защита от prompt injection
LLM triage в такой системе нельзя строить на доверии к входным данным. Findings приходят от сканеров, а сканеры анализируют недоверенные приложения, сервисы, ответы серверов, репозитории и артефакты. Внутри результата может оказаться что угодно: текст, похожий на инструкцию для модели, ссылка, payload, команда, фрагмент HTML, сообщение об ошибке или специально подготовленная prompt injection.
Поэтому команда обрабатывает вывод сканера как недоверенный input. В докладе упоминались специальные маркеры/токены вокруг данных и системные инструкции, которые запрещают модели смешивать данные finding с управляющими инструкциями. Это не серебряная пуля, но необходимый слой защиты.
Второй слой — sandbox. Если triage требует tools, система не даёт модели свободно выполнять произвольные команды. Есть заранее определённый allowlist: например, nmap, curl и другие проверочные утилиты. Команды проходят проверку, запускаются в изолированной среде, а выполнение ограничивается.
Для sandbox упоминались дополнительные меры защиты: read-only файловая система по смыслу фрагмента, ограничения capabilities, лимиты по времени и короткоживущие процессы. Для сетевых проверок важна и сетевая политика: sandbox должен иметь возможность сходить только на конкретный проверяемый адрес, а не во внешний мир или внутреннюю сеть без ограничений.
Такой подход не делает LLM «безопасной» магически. Он делает её частью контролируемого конвейера: модель рассуждает, но не получает произвольную власть над окружением.
Когда с LLM можно спорить
Интересная деталь продукта — возможность доуточнять результат triage. Если аналитик не согласен с выводом модели или хочет добавить контекст, он может продолжить взаимодействие с finding через чатовый сценарий: показать предыдущий ответ, добавить информацию и попросить модель пересмотреть решение.
Например, модель могла оценить finding как важный, но аналитик знает, что asset относится к staging и имеет другой risk profile. Или наоборот: сканер дал medium, но внутренний контекст делает finding более значимым. Такой дополнительный prompt позволяет не начинать анализ с нуля, а вести диалог вокруг конкретного lifecycle finding.
Это важное отличие от «чёрного ящика», который автоматически списывает находки. Хороший LLM triage должен оставлять человеку возможность вмешаться, добавить контекст, переоценить решение и зафиксировать результат.
30k findings превратились в 3k для ручного triage
Главный практический результат доклада — масштаб фильтрации.
На показанном дашборде было около 30 тысяч findings по периметру. Система распределяла их по severity critical/high/medium/low и автоматически triage-ила значительную часть потока. По словам спикеров, из этих 30 тысяч примерно 3 тысячи уходили на реальный ручной triage. В другом фрагменте доклада результат формулировался как около 90% автоматической обработки и примерно 7% ручного разбора аналитиками.
Здесь важна не математическая точность до процента, а порядок эффекта: человек перестаёт быть первой линией обработки для всех findings. Он становится последней линией для случаев, где автоматика не уверена или где цена ошибки выше.
Чтобы не списывать реальные проблемы в false positive, в системе используется порог уверенности. LLM возвращает confidence от 0 до 1, и если уверенность ниже 95%, finding не закрывается автоматически, а уходит человеку. То есть автоматизация работает не по принципу «модель сказала — значит правда», а по принципу «модель достаточно уверена в стандартном случае; всё сомнительное идёт на ручной triage».
Для команд, которые тонут в сработках, это ключевой операционный выигрыш. Даже если ручной triage остаётся, его объём становится управляемым. Аналитики разбирают не 30 тысяч карточек, а существенно меньший хвост неопределенности.
Что удалось автоматизировать
По докладу видно, что автоматизация затронула не одну кнопку, а несколько слоев процесса.
Во-первых, нормализацию входных данных. SARIF стал способом принимать findings из разных сканеров без постоянного переписывания ядра системы.
Во-вторых, deduplication и cross-scanner deduplication. Система не просто хранит все сработки, а пытается привести их к логическим findings и вести timeline.
В-третьих, первичный triage. Часть findings можно автоматически подтвердить или закрыть как false positive, если данных достаточно и confidence высокий.
В-четвёртых, интерактивную перепроверку через sandbox/tools. Для сетевых и инфраструктурных findings система может не только читать описание сканера, но и выполнять ограниченные проверки на asset.
В-пятых, категоризацию. Разные сканеры используют разные классы и описания проблем, а отчётность и управление требуют единой схемы. LLM помогает приводить findings к общему виду.
В-шестых, notifications и integrations. Уязвимость не должна умереть в отдельной вкладке. После triage её нужно передать владельцам, закрыть, подтвердить, завести задачу, обновить статус или подтянуть дополнительный контекст.
Отдельно звучал пример с контейнерными образами: команда работает с Harbor и проверяет не только финальный слой образа, а может расслаивать образ и сканировать отдельные слои. Этот фрагмент был из Q&A, поэтому его лучше воспринимать как пример направления интеграций, а не как центральную часть архитектуры.
Что стоит вынести другим AppSec-командам
Первый вывод: если вы вручную разбираете все findings, вы не обязательно делаете что-то неправильно. Часто это нормальная стартовая точка. Но на масштабе десятков тысяч сработок ручной triage всех результатов перестаёт быть устойчивым процессом. Нужна автоматизация, иначе команда будет выгорать и пропускать важное.
Второй вывод: начинать стоит не с LLM. Начинать стоит с модели данных, входного контракта, deduplication, cross-scanner deduplication и timeline. Если findings хаотично лежат в разных форматах, модель только добавит ещё один слой хаоса. LLM triage полезен тогда, когда уже понятно, что такое asset, что такое finding, как устроен lifecycle и где принимается решение.
Третий вывод: SARIF может быть практичным способом унифицировать вход. Даже если в названии стандарта есть Static Analysis Results Interchange Format, его можно использовать шире, если аккуратно сопоставить поля и не ломать семантику.
Четвёртый вывод: confidence и ручной fallback обязательны. Автоматическое закрытие findings должно происходить только при высокой уверенности и понятной логике. Всё, что ниже порога, идёт человеку. В докладе таким порогом называли 95%.
Пятый вывод: LLM triage с tools требует sandbox. Нельзя давать модели произвольный shell только потому, что finding надо проверить. Нужны allowlist команд, сетевые ограничения, лимиты времени, изоляция файловой системы и аккуратная работа с недоверенным input/output.
Шестой вывод: usability имеет значение. Тёмная тема, горячие клавиши, быстрый интерфейс и понятный workflow не выглядят как security-функции, но именно они определяют, сможет ли аналитик жить в системе каждый день.
Седьмой вывод: свой велосипед оправдан не всегда, но иногда он дешевле организационной боли. Если команда понимает свой поток сканеров, свои assets, свои integrations и свои требования к triage, небольшой внутренний продукт может закрыть задачу лучше, чем универсальная система, которую приходится постоянно обходить.
Практический чек-лист для тех, кто тонет в findings
Если в вашей команде похожая ситуация, полезно разложить задачу не как «внедрить AI», а как инженерный pipeline.
- Опишите все сканеры и типы findings: SAST, DAST, SCA, infrastructure/network-сканеры, секреты, контейнеры, периметр.
- Выберите единый входной контракт. SARIF — хороший кандидат, если его реально поддерживать последовательно.
- Нормализуйте severity critical/high/medium/low и категории, но сохраняйте оригинальные данные сканера.
- Постройте asset-модель: finding без asset-контекста быстро теряет практический смысл.
- Реализуйте deduplication сначала внутри одного сканера, затем cross-scanner deduplication.
- Ведите finding lifecycle/timeline: первое появление, повторные появления, сканер, evidence, решения, закрытия и переоткрытия.
- Добавьте автоматический triage для очевидных случаев.
- Подключайте LLM triage только там, где есть понятный формат входа, ожидаемый формат выхода и fallback на человека.
- Для проверок с tools используйте sandbox, allowlist команд и сетевые ограничения.
- Введите порог confidence, ниже которого finding всегда уходит на ручной triage.
- Сохраняйте объяснение решения: аналитик должен понимать, почему finding закрыт или подтвержден.
- Интегрируйте notifications и downstream-процессы, иначе triage останется отдельной красивой витриной.
Главная цель не в том, чтобы полностью убрать человека из vulnerability management. Цель в том, чтобы убрать человека из монотонной первой линии, где он кликает однотипные false positive и дубликаты. Человек должен разбирать спорные случаи, принимать риск-решения, добавлять контекст и заниматься тем, что требует инженерного суждения.
Итог
История Алексея Билая и Ильи Сафронова показывает, что «замена DefectDojo» в таком контексте — это не просто написать свою таблицу findings. Это построить систему, где сканеры говорят на общем входном языке, findings живут во времени, дубликаты схлопываются, разные источники связываются между собой, LLM triage берёт на себя понятную рутину, а sandbox ограничивает риск от автоматических проверок.
Самый сильный результат — не технологический, а операционный: 30k findings превращаются примерно в 3k случаев для ручного triage. Для AppSec-команды это означает меньше выгорания, меньше ручной рутины и больше внимания к тем уязвимостям, где действительно нужна человеческая экспертиза.