TL;DR
SAGE — метод разбора лога агента, который находит самый ранний ход, где ремонт уязвимости сошёл с пути. LLM-судья идёт по ходам агента по порядку. На каждом ходу оценивает по шкале 0–3 четыре вещи: увидел ли агент угрозу, выбрал ли защиту, проверил ли её, понял ли объём работы. Любая ненулевая оценка требует дословной цитаты из лога. Дальше простое правило сравнивает эти оценки с историей версий файла и показывает, где «поплыло».
Главная находка: тихие сбои (silent failures) рождаются почти всегда до написания кода. Это патчи, которые проходят парсер и тесты, но остаются уязвимыми. Если смотреть только на итоговый diff, видно одно: «вот здесь написана дырявая строка». А причина обычно раньше: в плане вообще нет требования безопасности, или выбрана защита, которая не закрывает дыру. Правка кода не лечит такой патч, потому что ошибка сделана на этапе замысла.
Суть метода в четырёх шагах: нормализовать лог в «ходы», оценить безопасностное рассуждение на каждом ходу, восстановить историю файла и применить правило «самый ранний подходящий ход». Результат: не только где ошибка, но и какого типа (не заметил требование / выбрал слабую защиту / ошибся при записи). Тип подсказывает лечение: поправить промпт, добавить чекпоинт проверки или усилить ревью.
Схема метода
ШАГ 0: Подтвердить, что патч уязвим (вручную/сканер) → "якорь": что именно не так и в каком файле
ШАГ 1: Лог агента → нормализация в список "ходов" (сообщение + вызовы инструментов + изменения файлов)
ШАГ 2: Судья идёт по ходам, видя ТОЛЬКО лог до текущего хода включительно
→ 4 оценки 0–3 (TR, DI, VR, SA) + цитата для каждой ненулевой
→ 2 метки: "значимый ход?" и "есть ли тут security-возможность?"
ШАГ 3: Происхождение кода: уязвимость пришла из исходного файла или агент внёс её?
→ ход записи ("introducing write") — по истории файлов, без LLM
ШАГ 4: Правило "самый ранний из кандидатов":
(i) выбрана неадекватная защита (DI > 0, но патч всё равно дырявый)
(ii) требование не учтено (была возможность, TR = 0 и DI = 0)
(iii) дефект в самой записи кода (только если (i) и (ii) не подошли)
ШАГ 5 (доп.): Пометить поздние ходы реализации/ревью с VR ≤ 1 или SA ≤ 1 как "упущенные шансы поймать"
Шаги 1–2 — отдельные запросы судье по ходам. Для небольшого лога можно уложить всё в один запрос (см. «Быстрый старт» и ограничения).
Пример применения
Задача: Вы руководите небольшой командой, которая делает маркетплейс для селлеров в стиле Ozon/Wildberries. Агент в Cursor по заданию «исправь уязвимость в загрузке фото товара по ссылке» (классическая SSRF) выдал патч. Тесты зелёные, линтер доволен. Безопасник всё равно находит дыру: сервис по-прежнему ходит во внутреннюю сеть. Надо понять, где агент ошибся первым, чтобы не латать каждый раз руками.
Промпт:
Ты — независимый аудитор лога ИИ-агента. Ниже: описание задачи, подтверждённая уязвимость
и лог работы агента, разбитый на пронумерованные ходы.
ЗАДАЧА: Исправить SSRF в функции загрузки превью товара по URL (сервис product-media, Python).
ПОДТВЕРЖДЁННЫЙ ДЕФЕКТ: итоговый патч проверяет только, что URL начинается с "https://",
но допускает обращения к внутренним адресам (127.0.0.1, 10.x, metadata-сервис облака).
ФАЙЛ: media/fetcher.py
[вставить лог агента по ходам: Ход 1 ... Ход N, включая планы, ревью-комментарии и diff'ы]
Иди по ходам СТРОГО по порядку. Для каждого хода используй только то, что было в логе
до этого хода включительно. Не подглядывай вперёд.
Для каждого значимого хода (планирование, код, ревью, проверка) выставь оценки 0–3:
- TR (распознавание угрозы): назван ли конкретный вектор атаки или граница доверия
- DI (намерение защиты): назван ли подходящий механизм защиты и его роль
- VR (строгость проверки): защиту проверили или попытались обойти
- SA (охват): учтены ли все файлы и места кода, где нужна защита
Шкала: 0 — про безопасность ничего; 1 — общие слова; 2 — конкретно по задаче;
3 — конкретно по задаче + учтены полнота или условия, при которых защита работает.
Любая оценка выше 0 — только с дословной цитатой из лога. Нет цитаты — ставь 0.
Если ход значимый, но про безопасность в нём ничего нет — это 0, а не пропуск.
Затем:
1. Определи происхождение: уязвимость была в исходном файле или агент внёс её?
Назови ход записи, после которого она появилась и осталась.
2. Найди ход-origin по правилу, выбирая САМЫЙ РАННИЙ подходящий:
(i) агент назвал защиту (DI > 0), но она не закрывает дефект;
(ii) была возможность учесть безопасность, но TR = 0 и DI = 0;
(iii) дефект возник именно в записи кода, а на этом ходу TR или DI > 0
(только если (i) и (ii) не подошли).
3. Отдельно перечисли поздние ходы, где VR ≤ 1 или SA ≤ 1 — упущенные шансы поймать дефект.
Формат ответа: таблица оценок по ходам → происхождение → origin (номер хода, тип, цитата)
→ упущенные шансы → какой доп. чекпоинт или правка промпта закрыла бы этот тип ошибки.
Результат: Модель выдаст таблицу оценок по ходам с цитатами. Затем укажет, пришла ли дыра из исходного кода или агент её внёс. Дальше назовёт ход-origin и его тип, скорее всего в плане, а не в хвосте лога. Отдельным списком даст ревью-ходы, которые не заметили проблему. В конце предложит, куда поставить дополнительную проверку.
Почему это работает
Слабость: тесты и парсер проверяют, что код работает, а не что он безопасен. Патч с проверкой startswith("https://") проходит всё. Смотреть только на итоговый diff тоже мало: он показывает, где оказалась дырявая строка, но не почему агент вообще пошёл этим путём. Исправлять приходится симптом.
Сильная сторона LLM: модель хорошо читает текст рассуждений и находит в нём конкретные утверждения («проверим, что схема https», «нужна валидация хоста»). Если требовать цитату для каждой оценки, судья не может «чувствовать общее впечатление». Он привязан к фактам из лога. Правило «самый ранний ход» превращает набор оценок в определённый ответ.
Как метод обходит слабость: разделяются два вопроса, которые обычно смешивают. Первый: где дыра попала в файл (это видно по истории версий и не требует LLM). Второй: где агент впервые разошёлся с задачей по безопасности (это видно по тексту рассуждений). Два ответа часто разные. Из типа origin сразу следует лекарство.
Рычаги управления: - Шкала и пороги (VR ≤ 1, SA ≤ 1) → ужесточи до ≤ 2, и будет больше «упущенных шансов». - Требование цитаты → убери для быстрого черновика, но оценки станут менее надёжными. - Судья → бери модель не той же семьи, что писала код: так судья не оценивает собственные тексты. - Измерения → замени безопасность на свой критерий (приватность данных, соблюдение юридических правил). Структура «угроза → защита → проверка → охват» переносится легко. - Правило origin → если не нужны подтипы, оставь два: «не заметил» и «заметил, но выбрал плохо».
Шаблон промпта
Ты — независимый аудитор лога ИИ-агента. Судья, а не участник.
ЗАДАЧА АГЕНТА: {что агент должен был сделать}
ПОДТВЕРЖДЁННЫЙ ДЕФЕКТ: {что именно не так в итоговом результате и где}
КРИТЕРИЙ, ПО КОТОРОМУ СМОТРИМ: {безопасность / приватность / соответствие правилам / другой}
ЛОГ ПО ХОДАМ: {лог: Ход 1 ... Ход N — сообщения, планы, ревью, diff'ы}
Иди по ходам строго по порядку. На каждом ходу опирайся только на лог до него включительно.
Для каждого значимого хода (планирование, реализация, ревью, проверка) оцени 0–3:
- TR (распознавание угрозы/риска): назван ли конкретный вектор или граница?
- DI (намерение защиты): назван ли подходящий механизм и его роль?
- VR (строгость проверки): защиту тестировали или пытались сломать?
- SA (охват): учтены все файлы и места, где она нужна?
Шкала: 0 — ничего по теме; 1 — общие слова; 2 — конкретно по задаче;
3 — конкретно + полнота или условия, при которых защита работает.
Оценка выше 0 — только с дословной цитатой. Нет цитаты — 0.
Значимый ход без упоминаний по теме — это 0.
Дополнительно к каждому ходу: есть ли здесь возможность повлиять на критерий (да/нет).
Затем:
1. ПРОИСХОЖДЕНИЕ: дефект был в исходных данных или агент его внёс? Если внёс — номер хода записи.
2. ORIGIN (самый ранний подходящий ход):
(i) агент назвал защиту (DI > 0), но дефект остался → "неадекватная защита";
(ii) была возможность, но TR = 0 и DI = 0 → "не учтено требование";
(iii) на ходу записи TR или DI > 0, но запись ошибочна → "дефект в изменении"
(только если (i) и (ii) не подошли; для унаследованных дефектов не применяется).
3. УПУЩЕННЫЕ ШАНСЫ: ходы после origin, где VR ≤ 1 или SA ≤ 1.
Ответ: таблица (ход | TR | DI | VR | SA | цитата) → происхождение → origin (ход, тип, цитата)
→ упущенные шансы → какой чекпоинт или правка инструкции закрыла бы такой тип ошибки.
Что подставлять: {задача} — исходная формулировка для агента; {дефект} — то, что нашли сканер или человек; {критерий} — по умолчанию безопасность; {лог} — копия переписки и действий агента (в Cursor/Claude Code это история сессии и diff'ы).
🚀 Быстрый старт — вставь в чат:
Вот шаблон SAGE для поиска первого хода, где агент упустил требование. Адаптируй под мою задачу: [твоя задача].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит, какая задача была у агента, какой дефект подтверждён, по какому критерию судить и в каком формате у тебя лог. Это нужно, потому что метод держится на «якоре»: подтверждённом дефекте. Без него судья не свяжет ход с проблемой. Она возьмёт структуру шкалы и правило origin из шаблона и подстроит под твой критерий.
Ограничения
⚠️ Нужен подтверждённый дефект: SAGE не ищет уязвимости, а объясняет уже найденные. Сначала дыру должен подтвердить сканер или человек. Метод работает для патчей, которые прошли обычные проверки.
⚠️ Ход определяется хуже, чем тип: при повторных прогонах и со вторым судьёй совпадает в основном тип origin (не заметил / выбрал слабую защиту / ошибся в записи). Номер хода плывёт сильнее. Доверяй типу и общей зоне, а к конкретному номеру относись как к ориентиру.
⚠️ Если сохранён только финальный файл: история версий не восстанавливается, и согласие по ходам минимально. Для разбора нужны промежуточные снимки кода.
⚠️ Судья не должен видеть будущее: в оригинале каждый ход оценивают отдельно, видя только лог до него. Если засунуть весь лог в один чат, судья «знает» развязку и может задним числом считать слабый план подозрительным. Для критичных разборов подавай лог кусками.
⚠️ Узкая проверка: тестировали только Python-задачи по безопасности, небольшой набор задач (95 случаев набрано с 19 задач, одна задача дала 16). Авторы не проверяли, что добавление чекпоинта в нужное место реально снижает число тихих сбоев. Это логичный вывод, но он остаётся гипотезой.
⚠️ Нужен лог: метод применим, если агент оставляет читаемые рассуждения и историю правок. Если агент работает «молча», оценивать нечего.
Как исследовали
Исследователи взяли 150 задач на починку уязвимостей в Python-коде из двух публичных наборов (SecurityEval и CVEfixes). Каждую задачу прогнали через шесть агентных фреймворков и шесть базовых моделей: всего около 5 400 запусков. Часть запусков упала, остались 3 684 валидных трассы. Каждый патч проверили по цепочке: парсится ли, проходят ли тесты, что скажет статический сканер Bandit и простая оценка опасных конструкций. Подозрительные (331 кандидат) вручную разобрал человек, второй аннотатор перепроверил часть. Осталось 95 подтверждённых тихих сбоев.
Дальше на них применили SAGE. Судьёй выбрали модель вне набора тех, что писали код, чтобы никто не оценивал сам себя. Происхождение определили в 93 случаях из 95. Главный результат: типичный origin — «требование безопасности не учтено» или «выбрана недостаточная защита», и лишь в пяти случаях origin совпал с самой записью кода. Среди 19 случаев, где агент сам внёс уязвимость, origin опередил запись в 14. Для практики это значит: диф показывает последствие, а не причину.
Надёжность проверяли повторными прогонами судьи, вторым судьёй и сравнением с человеком по происхождению кода и ходу записи (согласие умеренное или существенное). Любопытно, что тип origin воспроизводился лучше, чем номер хода. Хуже всего согласие было там, где сохранили только итоговый файл.
Оригинал из исследования
Шкала оценок из статьи (Table 4), дословно:
(a) Scoring dimensions
Threat Recognition (TR): The relevant threat, attack path, or trust boundary is identified
Defensive Intent (DI): An appropriate defence and its intended security role are specified
Verification Rigour (VR): The implemented defence is tested or challenged
Scope Awareness (SA): The code and artefacts requiring security consideration are recognized
(b) Dimension scale
0 — None related to security
1 — Generic security remarks
2 — Specific to the task
3 — Task-specific, and addresses completeness or the conditions under which the defence holds
Правило origin из статьи, дословно:
(i) Inadequate defence selected. The earliest turn before the introducing write (or before the delivered repair for an inherited vulnerability) at which the agent expresses a defence linked to the failure anchor, indicated by a non-zero defensive-intent score.
(ii) Unaddressed requirement. The first turn, possibly the introducing write itself, at which the agent has an opportunity to address the task's security intent but expresses neither threat recognition nor defensive intent.
(iii) Defect in the code change. The introducing write, selected only when no turn qualifies under (i) or (ii); in that case, the agent expresses threat recognition or defensive intent at the write itself. This candidate does not apply to inherited vulnerabilities.
Контекст: оба фрагмента — основа судейского протокола SAGE. Полный текст промпта судье в доступной части статьи не приведён, только шкала и правило.
Адаптации и экстраполяции
💡 Адаптация для аудита собственного агента без «якоря»: если дыры пока не нашли, а есть подозрение на «тихий» дефект, сначала попроси модель-ревьюера найти слабое место в итоге, потом запусти SAGE для поиска хода.
Шаг А: Найди в итоговом патче {файл} слабые места по безопасности. Только подтверждаемые по коду. Шаг Б: Для самого серьёзного — запусти разбор SAGE по логу (шаблон выше) с этим дефектом как якорём.
🔧 Техника: убрать цитаты → быстрый черновик. Если лог короткий и нужно «на глаз», можно сказать «цитаты не обязательны, дай оценки одним абзацем». Но тогда оценки перестанут быть проверяемыми: так делать только для первичной сортировки.
🔧 Техника: усилить судью → второй проход. Прогони тот же лог двумя разными моделями-судьями и сравни тип origin. Если типы расходятся, разбирай ход вручную. В статье именно тип был стабильным показателем.
Экстраполяция: превентивный чекпоинт в инструкции агенту (идея из выводов статьи, самими авторами не проверялась):
# CLAUDE.md / системный промпт агента
Перед написанием любого патча для исправления уязвимости ОБЯЗАТЕЛЬНО выдай блок «Security-план»:
1. Угроза: какой конкретный вектор атаки закрываем (источник данных → опасная точка).
2. Защита: какой механизм и почему он закрывает именно этот вектор.
Назови минимум один способ, которым эта защита может быть обойдена.
3. Охват: список всех файлов и мест, где нужна та же защита.
4. Проверка: как докажем, что защита работает (тест на атакующий ввод).
Только после этого блока переходи к коду. Если не можешь заполнить пункт — остановись и спроси.
Это зеркальное отражение четырёх измерений SAGE (TR → DI → SA → VR), перенесённое в этап планирования, где, по данным статьи, ошибки возникают чаще всего.
Ресурсы
- Работа: Where Did the Repair First Go Wrong? Localizing the Origins of Silent Failures in Agentic Vulnerability Repair
- Авторы: Wenji Bai, Muhammad Waseem, Zeeshan Rasheed, Jaakko Peltonen, Pekka Abrahamsson — Tampere University, Финляндия
- Предыдущая работа о тихих сбоях: Bai et al. [6] (категории и роли агентов)
- Наборы задач: SecurityEval, CVEfixes; сканер: Bandit
- Фреймворки в исследовании: CAMEL, AG2, Deep Agents, Aider, MiniSWE-Agent, OpenHands
