# Как мы написали свой велосипед Метаданные Доклад: «Как мы написали свой велосипед. Замена DefectDojo для инфровых и аппсечных уязвимостей» Спикеры: Алексей Билай, Илья Сафронов ## Вступление Работаете или не работаете? Да, спасибо. Меня зовут Илья. Это Леша. Я такой менеджер, Леша - программист. Мы расскажем про то, как мы проверяем appsec-уязвимости. Самое главное, что можно вынести из доклада: если вы проверяете все и делаете все руками, вы уже делаете правильно. Но готовое решение по vulnerability management можно сделать за два месяца. Если мы смогли за два месяца, то можно 90% инфраструктурных уязвимостей проверять не людьми. Здравствуйте. Всем привет. ## Почему понадобилось свое решение Начнем с того, что у нас было огромное количество уязвимостей и огромное количество сканеров. Это разные сканеры: сканеры исходного кода, инфраструктурные сканеры, сетевые сканеры, сканеры периметра и всего чего угодно. У каждого сканера свой формат, каждый выдает свои данные. При этом сами сканеры еще и ползят, шумят, генерируют ложные срабатывания, и в итоге для обработки всего этого нужна единая нормальная точка входа, чтобы все можно было в одном месте разбирать. Как и многие другие, мы столкнулись с этой проблемой и подумали, какой продукт можно посмотреть. Одним из основных и самых известных продуктов является DefectDojo. Мы посмотрели, какие у него есть плюсы и минусы. Среди минусов: низкая производительность, неполноценная дедупликация, а также то, что в него нельзя легко, из коробки, добавлять любые сканеры. То есть можно пользоваться в основном тем, что там уже есть. Если появляется что-то новое, приходится писать интеграцию. Еще, Леш, самый главный минус: темная тема за деньги. Да, это, естественно, было основным и самым главным фактором, почему мы решили, что все-таки стоит написать свой велосипед. И начали мы как раз с темной темы, чтобы потом можно было в холиварах разложить, что у нас все нормально. ## Что видно в продукте На слайде был показан дашборд нашего продукта. Там отображено около 30 тысяч находок за этот периметр. Видно распределение по уровням критичности: critical, high, medium, low. Но самое интересное - другая часть. Наш продукт, который мы реализовали для себя, автоматически триажит находки. Он автоматически раскидывает их: что-то подтверждено, что-то похоже на false positive. Самое главное, что из этих 30 тысяч только около 3 тысяч срабатываний уходят на реальный триаж живого человека. То есть туда попадает то, что продукт не смог протриажить и где не смог достоверно определиться сам: действительно ли это false positive, действительно ли это уязвимость, или информации недостаточно и нужно погружаться дальше. По большому счету это значит, что один человек не выгорает, а планомерно берет и разбирает оставшееся. Надо сказать, Алексей, что я лично знаю компании, в которых есть дежурство: люди на потоке сидят и вручную разбирают уязвимости сканеров. Это достаточно тяжелая работа. Самые суровые люди в компании. Если у вас такая же практика, надо подумать, что для этого нужны роботы, а не люди. ## Архитектура Дальше - небольшая техническая часть: как это все реализовано. На самом деле, ничего сверхъестественного нет. Система написана на Go [по звучанию] со стандартными библиотеками. В качестве хранилища внутри используется Postgres. Мы не используем дополнительные системы очередей: RabbitMQ, ZeroMQ и подобные. Так мы исключаем лишнее звено в инфраструктуре, за которым нужно следить и которое нужно обслуживать. Плюс удобно, что если ты мигрируешь бэкапы или делаешь что-то еще, все сразу находится в одном месте. На слайде было видно, что абсолютно любые сканеры могут погружаться в нашу систему. Дальше идут модули обработки. Также используется песочница - о ней позже. Есть стандартные модули оповещения и интеграции со смежными системами: для обогащения данных на входе, уведомлений и дальнейшей работы. Кстати, поддержка MAX реализована? Да, все-таки в MAX. ## Сканеры и SARIF Большинство сканеров, которые сейчас представлены на коммерческом, свободном и open-source-рынке, в той или иной степени уже поддерживают стандарт SARIF. Возможно, вы уже слышали про этот стандарт. У него есть несколько версий, и разные сканеры поддерживают его в разной степени. Наш сканер, который принимает SARIF, поддерживает стандарт в полном объеме, как он опубликован. Поэтому если у вас есть стандартные, нестандартные или вообще любые продукты, вы можете не ждать отдельной интеграции и не погружаться в то, как именно все это складывать. Можно использовать стандартный контракт, который уже описан. Я смотрел сайт SARIF: там около 200 страниц спецификации. Все, что может быть в сканере, там есть. Хочу отметить: несмотря на то, что SARIF называется Static Analysis Results Interchange Format, его структура такова, что туда можно подогнать абсолютно любые результаты: SAST, DAST, сетевые сканеры, секреты - все что угодно. Все это можно положить туда и обрабатывать через единый входной контракт. ## Дедупликация и история находок Мы реализовали не просто дедупликацию находок, но еще и cross-scanner deduplication. Если, грубо говоря, разные сканеры привезли что-то похожее и это логически одна находка, мы это тоже у себя денормализуем, а потом дедуплицируем. В итоге у нас есть четкая последовательность и историческая возможность посмотреть на timeline: когда это находилось, когда были повторные находки, кто находил, какой сканер, при каких условиях и так далее. ## LLM-триаж Самое интересное - LLM-триаж. У нас есть несколько вариантов. Первый вариант - триаж, который идет всего в один раунд. Это подходит, если речь про SAST и находки в исходном коде. Там ничего особо сложного нет: используются стандартные системные промпты, а дальше мы догружаем в них то, что приехало от сканера, как недоверенный input. Почему недоверенный? Потому что от сканера, как и от любого пользовательского ввода, может прийти что угодно. Этому нельзя сразу доверять. Если же это, например, сетевая находка, где требуется интерактивная или интеграционная составляющая с той системой, где мы нашли проблему, то составляется промпт и передается в sandbox. Формируются команды для разрешенных tools: например, nmap, curl и другие команды из заранее определенного списка. Они запускаются в sandbox, sandbox проверяет находку, прогоняет команды, генерирует output и отдает его обратно LLM. LLM склеивает то, что у нее было на первом раунде, с тем, что она фактически получила при повторной проверке на живых ресурсах. Потом еще раз все оборачивает, проверяет и смотрит, что получилось в итоге. По результату она выдает вердикт: подтвердилось, не подтвердилось, false positive или она не уверена. Дополнительно модель формирует свою уверенность, а также категоризирует находку. Это нужно потому, что разные сканеры могут использовать разные категории, а для отчетности проще приводить их к единому стандарту автоматически, а не руками. После ответа LLM принимается решение: таска может автоматически закрываться, подтверждаться или уходить дальше на ручной разбор. На экране были показаны системные промпты. Подробно на них не останавливались: если интересно, можно задавать вопросы. ## Защита от prompt injection и sandbox Из интересного - защита, по крайней мере попытка защиты, от prompt injection. Результаты сканеров, которые они нам присылают, - это обычный пользовательский ввод. Мы не должны ему доверять. Там могут быть любого рода инъекции: например, попытки сходить на внешний сервер, подключиться куда-то или выполнить что-то лишнее. Поэтому весь input мы оборачиваем в специальные токены, по сути маркеры. В системном промпте описано, что эта конструкция не должна ломаться. В sandbox тоже есть output, который является недоверенными данными, если мы что-то пропустили на первом этапе. Поэтому есть проверка только разрешенных команд, которые можно выполнять. Кроме того, поскольку все выполняется в sandbox, перед проверкой сетевой находки и перед поднятием окружения мы меняем сетевую политику так, чтобы sandbox мог сходить только на один конкретный адрес. Даже если мы что-то упустили на предыдущих этапах, это должно ограничить возможный ущерб. Получается многослойная система, где мы пытаемся обезопасить сами себя. На слайде были перечислены mitigation measures для sandbox: read-only файловая система [по смыслу], ограничения по capabilities, лимиты по времени. По большому счету это все рассчитано на короткоживущие процессы, которые должны отработать, сделать свои проверки и завершиться. ## Когда с LLM можно поспорить После того как LLM-триаж отработал, есть возможность прогнать находку еще через один слой. Например, если вы не уверены в результате, ретроспективно перепроверяете или пытаетесь найти похожие находки, можно догрузить в систему через интерфейс дополнительный промпт. Например, модель посчитала, что это не так важно, или наоборот сказала, что это очень важно. Вы можете добавить контекст: «нет, это staging, поэтому относись к этому проще». То есть это не просто тупой LLM-триаж. С ним можно поспорить? Да, с ним можно поспорить. У нас есть некий чат: видны предыдущие ответы, туда можно кинуть еще контекста, чтобы модель еще раз прошла по находке, но уже с новым вводом. Это похоже на паттерн систем с чатом, где можно уточнять результат. ## Сравнение моделей Мы проводили исследование качества проверки на нескольких открытых публичных моделях. Сразу скажу: в топе у нас получилась GLM версии 4.7 [по звучанию]. Модели, которая здесь не показана, у нас тогда еще не было. Даже вы сами тогда говорили, что «пятерка» не очень стабильная: подождите, мы выпустим 5.1. На что хочу обратить внимание: как говорил предыдущий спикер, не обязательно большие модели с огромным количеством параметров будут гораздо эффективнее и будут работать заметно умнее, чем другие модели. Мы проводили небольшой сравнительный эксперимент. Взяли 100 находок. Из них 50 были находками, которые мы сначала руками разобрали и точно знали результат. Потом в качестве «судьи» использовали Opus, который считается одной из самых умных моделей. Мы проверили, согласен ли он с нашими ответами. Opus согласился со всеми нашими вариантами. Потом добавили еще 50 срабатываний по находкам, разметили их Opus, а потом тоже проверили руками: да, они действительно были валидными или невалидными так, как было размечено. Поэтому за условную единицу правды мы взяли результаты Opus, а остальные модели прогоняли на тех же данных и с тем же промптом. Важно: промпты под конкретные модели не подкручивались. Да, подкручивание промптов может дать 2-3%, но не 20-30% прироста уверенности. На слайде можно было посмотреть результаты. Были и обидные строчки: например, GigaChat показал качество, близкое к угадыванию. По большому счету можно было рандомить, и результат был бы похожим. ## Откуда берутся targets Targets для сканеров и всего остального берутся из внешних систем. Если во внутренних корпоративных системах есть возможность обогатить данные, мы пытаемся подтягивать этот контекст. Мы не просто даем модели пустой ввод, а стараемся подкачать данные из внутренних систем, чтобы обогатить контекст, на который она опирается. ## Что получилось В итоге примерно за три месяца мы набрали около 30 тысяч срабатываний. Из них примерно 90% были отработаны автоматически, и только около 7% пришлось разбирать вручную аналитикам. Это гораздо лучше, чем 30 тысяч раз сидеть и кликать в DefectDojo. А еще у нас есть горячие клавиши, черная тема, всякие плюшки и удобства. Мы позаботились о людях: самое главное, чтобы людям было приятно и комфортно. По большому счету все. Если есть вопросы - задавайте. ## Вопросы и ответы ### Интеграция с GitHub и quality gates Вопрос: я тоже руководитель AppSec. У меня вопрос для личного пользования. Делали ли вы интеграцию с GitHub? Допустим, у нас есть линейка скриптов или ручек, которые постоянно нужно обновлять. Разработчик закидывает фичу, после этого quality gate ломается. Может ли ваш AI-триаж через Security Hub взять эту же уязвимость, обновить ее: например, устаревшая зависимость - и все? Ответ: я не совсем понял: интеграция с чем именно? С dependency policy, которые находятся в интернете? Уточнение: да, с dependency policy. Ответ: нет, с dependency policy не интегрированы. ### Локальные LLM как судьи Вопрос: использовали ли вы локальные LLM для триажа? Вы сказали, что используете Opus. Была ли в системе локальная LLM, которая лежит как judge? Ответ: как judge - нет. Не потому что мы не верим в локальные LLM. Просто не было смысла использовать локальную модель как судью. Нам нужно было сделать выборку, в которой мы на 100% уверены, чтобы большую часть выбрать руками, а потом прогнать локальные модели и посмотреть, насколько они близки к правде. Делать разных судей не было смысла. ### Интеграция с Grype и анализ контейнеров Вопрос: делали ли вы интеграцию или пытались ли подружиться с Grype, чтобы он смотрел конкретно Linux-пакеты, устарели они или нет, и закидывал это в ваш Security Hub? Ответ: да, есть, только у нас немного другая система. У нас используется Harbor. Из Harbor мы вытаскиваем образы. Но мы делаем это не в лоб. В Harbor уже есть встроенный анализатор Trivy [по звучанию]. Проблема Trivy в том, что он анализирует только финальный слой. А если где-то в середине слоя был, например, секрет: ты его написал, что-то сделал, а потом убрал, то Trivy его уже не найдет. Поэтому мы сделали так: берем из Harbor полностью образ, расслаиваем его и каждый слой проверяем отдельно. Результаты сканирования каждого слоя загружаем в систему и получаем обработку, в том числе по историческим слоям. ### Почему сравнивали модели разного размера Вопрос: спасибо за доклад, было интересно. Я не очень понял, зачем сравнивать 10B-модели с 400B и 120B. Это нелогично. Ответ: потому что это были модели, которые у нас доступны. Можно было посмотреть: а вдруг? Понятно, я предполагаю, что маленькая модель может не вывезти. Но вдруг сможет? Она стоит копейки, работает за пару-тройку секунд, а другая модель думает целую минуту. Поэтому надо проверить все возможные варианты. Это, может быть, не особо правильное сравнение, зато честное. ### Неизвестная модель и JSON Вопрос: что за последние модели? HFTS Pro [неразборчиво] - это имеет отношение к HFTS как компании или это что-то другое? Ответ: по-моему, нет. Это модель, про которую я в начале сам не знал, что это такое. Она странная: почти ничего не умеет, и зачем она нужна, я честно не очень понял. Комментарий из зала: Qwen3, Qwen Coder - вообще хорошие модели, поэтому мне было удивительно, что у вас она даже JSON не смогла сгенерировать. Ответ: не знаю, почему не смогла. Она постоянно отдавала битый JSON. Мы даем большой промпт и получаем большой output: запрос, ответ - и все, контекста нет, сессия не держится. В среднем у нас на вход и выход прилетает примерно по 4 тысячи токенов. Видимо, Coder Next не предназначен для того, чтобы сразу отдавать на выход по 4 тысячи токенов. Возможно, он предназначен для коротких подсказок: ему дали что-то, а он сказал, что дальше дописать. Может быть, с этим связано. Я не знаю. ### JSON, YAML и TOML Вопрос: был вопрос по поводу JSON. Насколько оптимально его использовать? Если брать YAML или TOML, они эффективнее по токенам, и вы экономите на времени, задержках и итоговом контексте. Ответ: можно, да. Но с тем же YAML я столкнулся с тем, что есть зависимость от пробелов, табов и отступов. От этого ломается синтаксис и структура ответа. JSON более структурирован за счет фигурных скобок. Мы теряем несколько токенов, но лучше потерять несколько токенов и получить нормальный валидный ответ, чем сидеть и ломать голову, почему съехал путь или почему что-то сломалось. Я за стабильный результат, который точно работает. ### Комментарий о качестве моделей Дальше был длинный комментарий из зала про сравнение моделей, качество Opus и ChatGPT-5/5.1 [по звучанию], а также про то, что разные модели лучше работают при разном формате промптов. Запись в этом месте сильно повреждена, поэтому подробности не восстанавливаются. ### Почему в сравнении был GigaChat Вопрос: смотрю на сравнение моделей, и у меня возникает вопрос: почему участвует GigaChat, когда можно использовать тот же DeepSeek, и он будет намного сильнее? Ответ: я уже говорил: это просто то, что было под рукой. Это не реклама каких-то моделей и не попытка сказать, что нам кто-то платил. Просто кинули кости на стол: что упало, то и проверили. ### Экономика решения Вопрос: есть ли у вас экономика? Например, расчет: вы сделали это решение, и оно восстановилось в PTE [неразборчиво] примерно на столько-то процентов? Ответ: смотри, джун от 100 тысяч рублей стоит - это зарплата для компании. А у нас по токенам получается примерно 300 долларов в месяц. Уточнение: у него такая же точность, сравнимая? Он так же не ошибается и эффективно решает задачу? Ответ: джун уходит в отпуск, спит, ест и так далее. По нашей практике, когда мы глазами смотрели, что система вырабатывает, качество абсолютно сравнимое. Комментарий: как джун? Ответ: да, как джун. Если промпт четкий, то она как джун. ### Как считали проценты Вопрос: я понимаю, что все эти проценты посчитаны с помощью benchmark. Кто считал проценты? Ответ: проценты считались на Opus 4.7 [по звучанию]. Уточнение: кто ставил проценты? Ответ: первые 50 находок размечали руками. Расскажу еще раз, как делалось. Взяли 100 находок. Из них изначально 50 были размечены нами, и мы точно знали результат. Потом в качестве судьи взяли Opus и спросили по этим 50 проверкам, какой результат. Он полностью совпал с нашим результатом. Потом взяли еще 50 других результатов, которые мы сами изначально не размечали. Разметили Opus, проверили руками - действительно совпадает. То есть мы взяли 100 находок, размеченных однозначно и достоверно. Потом взяли GLM и дали ей эти 100 задач: скажи, где false positive, где false negative и так далее. Она совпала с нашими ответами примерно на 80%. Эти проценты считала не нейронка, а обычная математика на основании находок, которые протриажили другие модели. Комментарий из зала: спрашиваю потому, что когда модель оценивает сама себя, она может подкручивать себе результат. Есть публичная информация, что китайские модели часто distilled на базе лучших моделей Anthropic или OpenAI. Ответ: нет, здесь простейшая математика. Модели даже не знали, что их тестируют. Мы просто подавали findings и просили определить false positive, false negative и так далее. Ответы писали в файл, потом смотрели, какие совпали с тем, что мы сами разметили. Все. ### Порог уверенности LLM Вопрос: вы сказали, что по примерно 30 тысячам находок для огромного количества было принято решение. Все-таки LLM вероятностная. Эти решения основываются на уверенности модели? Или это прямо 100% уверенность? Ответ: когда LLM отдает результат, она сама ставит вероятность того, насколько уверена. Уверенность может быть от 0 до 1. Если уверенность меньше 95%, мы считаем, что модель недостаточно уверена. Порог взяли 95%. Нам важно, чтобы модель не списывала реальные находки в false positive и чтобы false negative тоже не уходили автоматически. Где она на 100% уверена - окей, мы доверяем решению. Где не уверена - разбираемся вручную. Вот эти примерно 7% как раз и уходят на ручной разбор аналитикам. Спасибо спикерам. ## Неоднозначные места - В начале доклада фраза про «общечные продавценности» восстановлена как appsec-уязвимости по контексту доклада. - «Система написана на Go» восстановлено по звучанию из ASR («на Go-шке»), но в записи это место искажено. - Название модели «GLM версии 4.7» восстановлено по звучанию. В сырой записи встречается «BLM 4.7»; точное название и версия сомнительны. - «Opus 4.7»/«Opus» в Q&A восстановлено по контексту как модель-судья. Версия звучит неуверенно. - Название модели «HFTS Pro» передано по сырой записи; точное название неясно. - «Qwen3», «Qwen Coder», «Coder Next» восстановлены по контексту обсуждения моделей, но ASR в этом фрагменте сильно искажена. - В ответе про контейнерные образы «Trivy» восстановлен по контексту Harbor и анализа образов; в ASR звучит как «3D». - Вопрос про экономику содержит «PTE» или похожий термин, точное слово неразборчиво. - Один длинный комментарий из зала про сравнение моделей и промпты сильно поврежден ASR; он оставлен кратким пересказом без деталей. - «read-only файловая система» в mitigation measures для sandbox восстановлена по смыслу из поврежденного фрагмента; точное перечисление мер на слайде неизвестно.