7 760 статей
arXiv:2611.06135 88 4 нояб. 2026 г. FREE

SBVUL: двухфазный поиск уязвимостей кодовым агентом с разделением охотника и верификатора

КЛЮЧЕВАЯ СУТЬ
Проблема: когда один агент ищет баги во всей функции сразу, он тонет в ложных тревогах и сам оправдывает свои догадки. Метод SBVUL позволяет находить в репозитории конкретные уязвимые строки кода без десятков пустых предупреждений. Фишка метода: поиск и проверка разнесены по двум изолированным сессиям без общей памяти. Первый агент ищет строки опасных операций, а второй в чистом чате проверяет поток данных и отсекает все недоказанные гипотезы в категорию неопределённых.
Адаптировать под запрос
⚡

TL;DR

SBVUL — двухфазная схема работы кодового агента, которая ищет в репозитории конкретные эксплуатируемые строки кода вместо общей оценки «функция уязвима или нет». Два отдельных агента с инструментами чтения и поиска по файлам (Read, Grep, Glob) последовательно находят подозрительные операции и проверяют их по графу вызовов.

Когда одну модель просят сразу найти уязвимости во всей функции, она либо тонет в ложных срабатываниях, либо указывает на место будущего патча (проверку if), а не на саму опасную операцию. Более того, если тот же агент в одном диалоге и выдвигает гипотезу, и проверяет её, он попадает в ловушку подтверждения: оправдывает собственную догадку косвенными уликами вместо строгой трассировки данных.

Метод разделяет поиск и проверку между двумя изолированными сессиями без общей истории диалога. Первый агент («Охотник») широко исследует функцию, вызывающий и вызываемый код и выписывает список строк-кандидатов, а второй агент («Верификатор») с чистым контекстом проверяет поток управления и данных по каждому кандидату и относит все недоказанные допущения в категорию uncertain (не определено).


🔬

Схема метода

Шаги выполняются в двух отдельных запросах (сессиях агента) с доступом к инструментам поиска по коду (Read, Grep, Glob), но без общей истории чата:

ШАГ 1 (Сессия 1 — Охотник): Анализ функции + поиск callers/callees по репозиторию → JSON-список строк-кандидатов [{"line", "code", "reason"}]
ШАГ 2 (Сессия 2 — Верификатор): Трассировка потоков данных и проверок для каждого кандидата с нуля → JSON-вердикты [confirmed / ruled out / uncertain]
ШАГ 3 (Правило фильтрации): Отсечь ruled out и uncertain → оставить в отчёте только confirmed

🚀

Пример применения

Задача: Вы проводите аудит безопасности бэкенд-сервиса приёма вебхуков оплаты от российских платёжных шлюзов (ЮKassa и СБП) перед релизом и хотите с помощью агента в Claude Code или Cursor найти реальные дыры в обработке входящих платежей без десятков пустых предупреждений.

Промпт:

Запрос 1 (Сессия 1 — поиск кандидатов):

Ты — специализированный эксперт по поиску уязвимостей в коде. Твоя задача — определить, содержит ли целевая функция `process_sbp_webhook` в файле `services/payment/webhook_handler.py` эксплуатируемую уязвимость.

## Фаза 1 — Исследование и поиск строк-кандидатов
Начни с чтения и разбора исходного кода целевой функции. Используй инструменты поиска по файлам и проанализируй:
- Структуру и граф вызовов: найди все места, откуда вызывается функция (callers), и посмотри, что она делает с аргументами.
- Источники заражения и опасные стоки (taint sources & sinks): проследи, откуда приходят параметры (внешние JSON-поlya вебхука, заголовки, параметры запроса) и доходят ли они до опасных операций (SQL-запросы, вызовы команд, запись в баланс, десериализация, работа с памятью и индексами).
- Логические уязвимости: пропущенная валидация подписи, нарушения авторизации, состояния гонки или устаревшие ссылки после вызовов с побочными эффектами.

Строка, которую ты указываешь для каждого кандидата, должна быть именно тем оператором, выполнение которого совершает небезопасное действие (а не местом, где не хватает проверки).
Остановись, когда кандидаты исчерпаны. Выведи JSON-массив — по одному объекту на кандидата:
[{"line": <номер строки числом>, "code": "<краткий фрагмент кода>", "reason": "<почему строка может быть эксплуатируемой>"}]

Запрос 2 (Сессия 2 — чистый чат, передаём только код функции и JSON из Фазы 1):

Ты — специализированный эксперт по верификации уязвимостей.
Вот список строк-кандидатов из Фазы 1 для функции `process_sbp_webhook` (`services/payment/webhook_handler.py`):
[вставить JSON из Фазы 1]

## Фаза 2 — Проверка каждого кандидата
Подтверди или опровергни каждого кандидата с помощью инструментов чтения файлов и поиска по репозиторию:
- Перечитай точные строки вокруг каждого кандидата в контексте файла.
- Найди другие места вызова функции и проверь, не валидируются ли данные выше по стеку (в middleware, роутере или вызывающем коде).
- Проверь определения типов, схем данных и граничных условий.

Вердикт обязан опираться только на фактически собранные инструментами доказательства, а не на допущения. Если для уязвимости нужно условие, которое ты не проверил по коду, ставь вердикт `uncertain`.
Для каждого кандидата укажи: `confirmed` (подтверждено), `ruled out` (опровергнуто) или `uncertain` (не определено). Прекращай расследование кандидата, как только доказательства стали однозначными.
Выведи JSON-массив:
[{"line": <номер строки>, "verdict": "confirmed | ruled out | uncertain", "explanation": "<доказательство с путём потока данных>"}]

Результат: В первой сессии агент пройдёт по вызовам обработчика вебхука и соберёт 3–5 подозрительных строк в JSON (например, прямой вызов начисления средств на счёт или запрос в базу данных). Во второй сессии новый агент проверит middleware и вызывающие модули: отбросит кандидатов, защищённых проверкой HMAC-подписи на уровне роутера (ruled out), пометит спорные места без явных улик как uncertain и оставит confirmed только там, где неочищенный параметр реально доходит до опасной операции.


🧠

Почему это работает

Когда LLM ищет баги в один проход, она смешивает две противоположные задачи: широкую генерацию идей (высокая полнота) и строгую отбраковку (высокая точность). Если заставить модель быть осторожной сразу, она пропустит реальные дыры. Если попросить искать всё подряд, разработчик утонет в ложных тревогах.

Сильная сторона современных моделей с инструментами — умение целенаправленно ходить по файлам репозитория (Grep, Read), когда им задана узкая цель. Если разделить процесс на два независимых этапа без общей памяти, второй агент не видит черновых рассуждений первого. Он получает только сухую гипотезу (номер строки и причину) и проверяет её как внешний аудитор.

Ключевой механизм защиты от галлюцинаций здесь — принудительная категория uncertain и требование привязки к конкретной строке опасной операции (sink). Модели запрещено угадывать: если она не нашла в коде подтверждения того, что атакующий контролирует переменную, она обязана списать кандидата в uncertain, который при финальной фильтрации отбрасывается.

Рычаги управления: - Инструкция по строке-мишени → требуй указывать именно строку выполнения опасной операции, иначе модель будет указывать на if-проверки и заголовки функций. - Правило для uncertain → для критичных модулей можно просматривать uncertain глазами, а для быстрой проверки в CI оставлять только confirmed. - Лимит шагов поиска → ограничь число вызовов инструментов (например, до 50–80 шагов на фазу), чтобы агент не уходил в бесконечный обход дерева вызовов.


📋

Шаблон промпта

Ты — специализированный эксперт по поиску уязвимостей и ошибок в коде на {язык_или_стек}. Твоя задача — определить, содержит ли функция `{имя_функции}` в файле `{путь_к_файлу}` эксплуатируемую уязвимость.

## Phase 1 - Explore & Hunt for Candidate Statements
Начни с чтения и анализа исходного кода целевой функции. Используя инструменты чтения и поиска по репозиторию, проанализируй:
- Structure & call graph: найди вызывающие функции (callers) и посмотри, что целевая функция делает с аргументами и где используется возвращаемое значение (callees).
- Taint sources & sinks: проследи, могут ли параметры формироваться из недоверенного внешнего ввода и доходить без проверки до опасных операций ({типы_опасных_операций}).
- Logic-level patterns: пропущенная валидация, проверки прав доступа, устаревшие ссылки после вызовов с побочными эффектами, ошибки работы с размерами и индексами.

Строка, которую ты указываешь для каждого кандидата, должна быть именно тем оператором, выполнение которого совершает небезопасное действие.
Выведи JSON-массив — по одному объекту на кандидата:
[{"line": <номер строки числом>, "code": "<краткий фрагмент кода>", "reason": "<объяснение уязвимости>"}]

---
[Запускается в НОВОЙ сессии агента без истории Phase 1]

Вот список кандидатов из Phase 1 для функции `{имя_функции}` (`{путь_к_файлу}`):
{json_кандидатов_из_фазы_1}

## Phase 2 - Confirm Each Candidate Statement
Подтверди или опровергни каждого кандидата с помощью доступных инструментов поиска и чтения кода:
- Перечитай точные строки вокруг кандидата в контексте файла.
- Найди другие места вызова функции, определения типов, макросов и проверок границ в соседнем коде.
- Проследи поток управления и данных от источника ввода до строки-кандидата.

Вердикт обязан опираться только на фактически собранные доказательства, а не на предположения. Если ты не проверил предположение по коду, кандидат получает статус `uncertain` (пустой результат поиска сам по себе не является доказательством).
Для каждого кандидата запиши вердикт: `confirmed`, `ruled out` или `uncertain`. Прекращай проверку кандидата, как только доказательства однозначны.
Выведи JSON-массив:
[{"line": <номер строки>, "verdict": "confirmed | ruled out | uncertain", "explanation": "<доказательство с путём потока управления или данных>"}]

Что подставлять: - {язык_или_стек} — язык проекта (например, C/C++, Python/FastAPI, Go, TypeScript). - {имя_функции} и {путь_к_файлу} — проверяемая функция и файл в репозитории. - {типы_опасных_операций} — характерные для стека стоки (например, memcpy/free для C++, SQL/eval/subprocess/платёжные транзакции для веба). - {json_кандидатов_из_фазы_1} — JSON-ответ первого агента, который передаётся во вторую сессию.

🚀 Быстрый старт — вставь в чат:

Вот шаблон двухфазного аудита кода SBVUL. Адаптируй под мою задачу: [твоя задача]. 
Задавай вопросы, чтобы заполнить поля.

[вставить шаблон выше]

LLM спросит, на каком языке написан проект, какую функцию или модуль нужно проверить и есть ли у вас агент с доступом к файлам (например, Claude Code или Cursor) — потому что методу нужно настроить список опасных операций под ваш стек и развести инструкции на два отдельных запуска. Она возьмёт паттерн из шаблона и адаптирует под задачу.


⚠️

Ограничения

⚠️ Ложные тревоги на уже пропатченном коде: Даже после второй фазы модели ошибочно подтверждают уязвимости в значительной доле уже исправленных функций. Если защитная проверка написана нестандартно или разнесена по нескольким функциям, агент может её не распознать и выдать ложный confirmed.

⚠️ Слепота к уязвимостям из-за отсутствующего кода: Метод ищет конкретную опасную строку внутри функции. Если суть бага в том, что в функции вообще забыли написать действие (например, не сбросили флаг состояния), а взлом происходит совсем в другом модуле, привязка к строкам не сработает.

⚠️ Высокий расход токенов и времени на глубоком анализе: Каждая фаза делает десятки вызовов инструментов чтения и поиска по репозиторию. Включение максимального режима рассуждений (thinking) на слабых или медленных моделях может приводить к таймаутам без прироста точности.


🔍

Как исследовали

Команда проверила подход на 200 парах реальных функций C/C++ из бенчмарка PrimeVul Paired (охвачено 104 открытых проекта и 45 типов уязвимостей CWE), где каждая пара состоит из уязвимой функции и её же версии после реального патча безопасности. Сначала авторы с помощью Opus 5.5 и двойной ручной выверки разметили в этих 200 функциях 380 реально эксплуатируемых строк и обнаружили важный факт: в 56% уязвимых функций патч вообще не трогает саму опасную строку, а просто вставляет проверку выше по коду — поэтому старые бенчмарки, сверявшие ответ модели со строками из git diff, оценивали поиск заплатки, а не поиск самой дыры. Затем исследователи прогнали двухфазного агента на четырёх моделях (Qwen 3.8 27B, DeepSeek V4.1 Flash, Claude Sonnet 5.5 и Claude Opus 5.5) через LangChain и Claude Code Agent SDK, причём мало просто угадать номер строки — судья на базе LLM сверял, правильно ли агент объяснил корневую причину бага. В итоге Фаза 1 находила реальные уязвимые строки в 56–77% функций, а Фаза 2 с Opus 5.5 правильно классифицировала 42,0% пар (против 33,8% у лучшего предыдущего метода VulTriage и 18,6% у мультиагентных дебатов VulTrial), а по критерию с проверкой объяснения CORRECT достигла 75,5% против 17,2% у VulInstruct. При этом исследование честно вскрыло слабую зону всех современных LLM: в 23–61% уже исправленных функций модели всё равно находили «подтверждённую» уязвимость, так что вердикты агента нужно использовать как точечный навигатор для ревьюера, а не как безошибочный блокиратор релиза.


💡

Адаптации и экстраполяции

1. Адаптация техники

🔧 Техника: Поиск первопричины продакшен-багов по логу ошибки → точная локализация сломанного оператора

Замените поиск уязвимостей на поиск строки, порождающей некорректное состояние данных (например, NaN в расчёте скидки или дублирование заказа):

## Phase 1 - Hunt for Bug-Origin Statements
Найди все строки в функции и вызываемых методах, где изменяется поле `{имя_поля}` или вычисляется итоговая сумма. Выпиши каждую строку-кандидат в JSON.

## Phase 2 - Verify Data Flow
В новой сессии проверь по коду репозитория, какие из кандидатов могут получить `{ошибочное_входное_значение}` в обход существующих проверок. Непроверенные гипотезы помечай как `uncertain`.

2. Экстраполяция

В проектах с Claude Code или Cursor оформи Фазу 1 и Фазу 2 как двух субагентов (.claude/agents/vuln-hunter.md и .claude/agents/vuln-verifier.md). Главный агент при ревью пул-реквеста сначала вызывает vuln-hunter на изменённых функциях, забирает чистый JSON-массив кандидатов и передаёт его в vuln-verifier, который стартует с пустым контекстом и возвращает разработчику только подтверждённые (confirmed) строки с трассировкой.


🔗

Ресурсы

Исследование: SBVUL: Statement-Based Vulnerability Detection (arXiv:2611.06135) Репликационный пакет и промпты: https://doi.org/10.5281/zenodo.23881951 Авторы: Martin Pinzger, Katharina Stengg, Christian Macho (University of Klagenfurt), Stefan Schöberl (Software Competence Center Hagenberg), Víctor Mayoral Vilches (Alias Robotics)


📋 Дайджест исследования

Ключевая суть

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

Принцип работы

Раздели аудит кода на двух независимых агентов, как следователя и судью в разных кабинетах. Первый агент («Охотник») широко копает вызовы функции и собирает подозреваемые строки, где выполняется опасное действие. Второй агент («Верификатор») открывает чистую сессию без черновиков первого. Любое непроверенное по коду допущение принудительно списывается в статус uncertain (не определено). В финальный отчёт попадают только железно доказанные дыры.

Почему работает

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

Когда применять

Аудит безопасности и поиск багов в коде → конкретно для проверки функций с внешними данными (вебхуки оплаты, авторизация, SQL-запросы, работа с памятью) через агентов с поиском по файлам (Claude Code, Cursor). НЕ подходит для ошибок, где опасный код вообще забыли написать (например, пропущен сброс флага в другом модуле).

Мини-рецепт

1. Запусти Сессию 1 (Охотник): дай агенту имя функции и попроси через поиск по файлам найти конкретные строки с опасными операциями в формате JSON [{"line", "code", "reason"}].
2. Открой чистую Сессию 2 (Верификатор): передай только код функции и JSON-список кандидатов без истории первого чата.
3. Включи правило жёстких улик: прикажи проверить путь данных от ввода до строки и ставить статус uncertain при любом непроверенном условии.
4. Отфильтруй итог: отбрось статусы ruled out и uncertain, оставив для разбора только confirmed.

Примеры

[ПЛОХО] : Найди все уязвимости в функции process_sbp_webhook и напиши, где добавить проверки.
[ХОРОШО] : Сессия 1: Проследи граф вызовов process_sbp_webhook. Выведи JSON со строками, где внешние данные доходят до опасной операции: [{"line": число, "code": "фрагмент", "reason": "причина"}]. Сессия 2 (новый чат): Проверь каждого кандидата из JSON по коду роутеров и схем. Без строгих улик ставь вердикт uncertain. Выведи: [{"line", "verdict": "confirmed|ruled out|uncertain", "explanation"}]
Источник: SBVUL: Statement-Based Vulnerability Detection
ArXiv ID: 2611.06135 | Сгенерировано: 2026-11-09 07:06

Проблемы LLM

ПроблемаСутьКак обойти
Модель оправдывает свои гипотезы при самопроверке в одном чатеПросишь модель найти ошибки и сразу проверить их в одном диалоге. Видя свои прошлые рассуждения, она ищет косвенные подтверждения своей догадки. Ложные срабатывания проходят проверку как настоящие ошибкиРаздели поиск и проверку на два отдельных чата без общей истории. Первый чат выдаёт только список кандидатов [{"line", "code", "reason"}]. Второй чат проверяет этот список с нуля
Модель указывает на место будущего исправления вместо самой ошибкиПросишь найти уязвимость или ошибку во всей функции целиком. Модель выдаёт общую оценку или указывает на блок if, где не хватает проверки. Сама опасная строка кода остаётся ненайденнойПотребуй указать точную строку, где выполняется опасное действие: [{"line": <номер>, "code": "<фрагмент>"}]. Запрети указывать строки с пропущенными проверками

Методы

МетодСуть
Двухфазный аудит в изолированных сессиях — точность без ложных тревогРаздели поиск и проверку на два запуска без общей памяти диалога. В первой сессии поручи собрать максимум подозрительных строк в массив [{"line", "code", "reason"}]. Во второй сессии передай только код и этот список. Поручи проверить поток данных по коду и вынести вердикт: confirmed, ruled out или uncertain. Почему работает: Поиск требует широкого охвата, а проверка — строгой отбраковки. Чистый контекст во второй сессии убирает предвзятость подтверждения. Второй агент не видит черновых догадок первого и судит только по фактам из кода. Когда применять: Аудит кода, поиск уязвимостей, проверка сложных документов с перекрёстными ссылками. Когда не работает: Ошибка вызвана полным отсутствием нужного блока кода, а не конкретной строкой
Статус неопределённости для недоказанных гипотез — отсечение домысловДобавь в схему ответа проверяющего агента обязательный третий статус uncertain наряду с confirmed и ruled out. Запрети подтверждать проблему без полной трассировки данных по файлам. В финальный отчёт бери только confirmed. Почему работает: При двоичном выборе (да или нет) модель склонна угадывать и выдавать сомнительные допущения за факты. Легальная категория uncertain забирает на себя все непроверенные гипотезы и очищает итоговый список. Когда применять: Автоматическая проверка кода, фактов или нарушений правил с жёстким требованием к точности. Когда не работает: Задачи мозгового штурма, где важны даже слабые догадки

Тезисы

ТезисКомментарий
Проверка собственных гипотез в том же диалоге снижает строгость критикиКогда модель видит в истории чата свои собственные аргументы в пользу гипотезы, она защищает их вместо объективной проверки. Перенос проверки в новую сессию без истории превращает модель в независимого аудитора. Применяй: Передавай во второй запрос только сухой список найденных пунктов без черновых рассуждений первого запроса
Привязка ответа к конкретной строке действия фокусирует поиск инструментовКогда от модели требуют оценить большой блок целиком, она распыляет внимание и выдаёт размытые суждения. Требование назвать точный номер строки опасного оператора заставляет модель проследить путь данных от входа до конкретной команды. Применяй: Вместо вопроса "есть ли тут ошибка" требуй формат {"line": <номер>, "code": "<строка действия>"}
📖 Простыми словами

SBVUL: Statement-Based Vulnerability Detection

arXiv: 2611.06135

Современные сканеры безопасности работают как ленивые охранники: они просто тыкают пальцем в целую функцию и говорят, что там «что-то не так». SBVUL меняет правила игры и заставляет нейронку искать не абстрактную проблему, а конкретную строку кода, которую можно взломать. Суть в том, что модель перестает гадать по кофейной гуще и начинает работать как настоящий хакер, который идет по следу данных от ввода пользователя до опасной операции.

Это как если бы вы искали утечку газа в огромном доме. Обычный подход — сказать, что «в районе кухни пахнет», и заставить вас перебирать все трубы. SBVUL работает иначе: сначала один агент обходит все комнаты с датчиком и метит подозрительные стыки, а второй берет мыльный раствор и проверяет каждый конкретный шов, пока не запузырится. Формально оба ищут утечку, но только второй метод избавляет вас от необходимости разносить стены в поисках фантомной проблемы.

Вся магия строится на двух фазах, где у агентов отшибает память о действиях друг друга, чтобы не было предвзятости. Сначала агент-поисковик с помощью инструментов Grep и Glob выцепляет подозрительные места, вроде обработки вебхуков от платежек. Затем в дело вступает агент-верификатор: он строит граф вызовов и проверяет, реально ли «протащить» вредоносную нагрузку в ту самую строку. Если модель видит, что данные от ЮKassa проходят через три фильтра, она не будет спамить вам в консоль — она ищет только те дыры, которые реально текут.

Этот принцип легко переносится с поиска багов на любую сложную аналитику. Тестировали на уязвимостях, но двухфазная проверка спасет вас везде, где нужно отделить зерна от плевел: при проверке юридических договоров, аудите логов или рефакторинге огромных легаси-проектов. SEO для кода уходит в прошлое, наступает эра точечной хирургии, где агент не просто «читает текст», а использует инструменты поиска как свои руки.

Главный вывод: хватит просить нейронку «найти ошибки» одним махом — это путь к горе мусорных уведомлений. Разделяйте поиск и проверку, заставляйте модель работать с конкретными строками, а не с файлами целиком. SBVUL доказывает, что точность растет в разы, когда вы лишаете агента общей истории чата и даете ему четкую роль. Либо вы внедряете такую двухфазную схему, либо продолжаете тонуть в ложноположительных срабатываниях, пока реальная дыра ждет своего хакера.

Работа с исследованием

Адаптируйте исследование под ваши задачи или создайте готовый промпт на основе техник из исследования.

0 / 2000
~0.5-2 N-токенов ~10-30с
~0.3-1 N-токенов ~5-15с