TL;DR
TRACER — рабочий процесс (его можно загрузить в агента как скил), который проверяет код от агента не по финальному снимку, а по истории правки. Он смотрит, какие функции агент менял, и берёт его собственные объяснения («вход уже проверен выше», «вызывающий код надёжен»). Эти объяснения считаются утверждениями, которые надо проверить по текущему коду, а не фактами. Когда уязвимость починена, процесс записывает заметку: что должно оставаться верным и каким тестом это проверить. Любая следующая правка обязана этот тест пройти.
Главная боль: агент пишет патч, все тесты зелёные, а проверка или ограничение пропущены. Из 354 проверенных патчей 350 прошли функциональные тесты, а тесты безопасности прошли только 69. Сканер безопасности (CodeQL, Semgrep) указывает на нужную функцию лишь в 13% случаев. Он видит финальный код и не знает, что агент предполагал и что выкинул по дороге. Вторая беда: починку незаметно ломает следующая задача. Просьба «добавь совместимость со старыми конфигами» вернула небезопасную загрузку файлов, и все тесты остались зелёными, потому что тест не помнит, зачем было ограничение.
Метод решает обе проблемы. Первая: отслеживать изменения по функциям, а не по строкам диффа, и подкладывать проверяющей модели объяснения агента как гипотезы. Вторая: после починки хранить рядом с кодом инвариант (то, что должно оставаться верным) и граничный тест (проверка этого условия), которые вызываются при любой следующей правке. Починка принимается только через «ворота»: подтверждённая оценка, проверка вызывающего кода и прохождение функциональных тестов.
Схема метода
ЭТАП А — ревью патча агента
ШАГ 1: Найти изменённые функции и классы (не строки диффа) → список символов
ШАГ 2: Привязать к каждой функции объяснения агента из его лога → цитата + план проверки
ШАГ 3: Проверить каждое утверждение по ТЕКУЩЕМУ коду → подтверждено / опровергнуто
ШАГ 4: Записать гипотезу в журнал решений (JSON): функция, граница входа, подозреваемый изъян
ШАГ 5: Ворота ремонта: подтверждённая оценка + проверка потребителей функции + функциональные тесты
→ патч принят / отклонён
ЭТАП Б — защита починки на будущее
ШАГ 6: Записать заметку: инвариант + причина + граничный тест (хранится отдельно от основного кода)
ШАГ 7: Любая новая правка проходит: обычные тесты + тесты новой фичи + граничный тест
ШАГ 8: Провал → вернуть агенту упавшую проверку, одна попытка; не вышло → оставить защищённый код
Всё это один процесс внутри агента-хозяина. Отдельные запросы нужны только на этапах А и Б.
Пример применения
Задача: Команда интернет-магазина на Python принимает платежи через вебхуки ЮKassa. Claude Code переписал обработчик вебхука. Тесты зелёные, но агент пояснил в логе: «подпись уже проверена в middleware». Через месяц менеджер просит: «Поддержи старые уведомления от партнёра, у них нет подписи». Нужно проверить патч и сделать так, чтобы эта просьба не открыла обработчик для подделок.
Промпт:
Ты ревьюер безопасности кода, написанного агентом. Работай по процессу ниже.
Изменённая функция: handle_payment_webhook (файл payments/webhooks.py)
Задача, которую выполнял агент: «Переписать обработчик вебхука ЮKassa, добавить идемпотентность по payment_id»
Из лога агента (его объяснения, не код):
«Подпись запроса уже проверена в middleware, поэтому в обработчике
достаточно разобрать JSON и обновить статус заказа.»
1. Рассматривай слова агента как ГИПОТЕЗУ, а не факт. Выпиши утверждение дословно.
2. Составь план проверки: какие функции и вызывающий код надо прочитать,
чтобы убедиться, что подпись действительно проверяется ДО handle_payment_webhook
на всех путях вызова.
3. Проверь по текущему коду. Вывод: подтверждено / опровергнуто / недостаточно данных.
4. Если опровергнуто — предложи минимальный патч. Принимай его, только если
(а) оценка подтверждена кодом, (б) проверены все места вызова функции,
(в) функциональные тесты проходят.
5. После починки напиши заметку: инвариант («обработчик не выполняется без валидной подписи»),
причина, и граничный тест, который отправляет запрос без подписи и ожидает отказ.
6. Для любой следующей правки в этом файле: прогоняй этот тест. Если он падает,
не меняй инвариант, а расширь решение внутри него.
Результат: Модель выпишет цитату агента и план проверки. Затем пройдёт по цепочке вызовов, и по каждому пути будет видно, проверяется ли там подпись. Если дыра нашлась, придёт минимальный патч с отметкой, какие проверки он прошёл. В конце будет заметка с инвариантом и текстом граничного теста. Когда позже придёт просьба про «старых партнёров без подписи», эта заметка и тест не дадут просто отключить проверку. Модель должна будет предложить другой путь, например отдельный закрытый эндпоинт или список разрешённых IP.
Почему это работает
Слабость. Сканер и тесты видят только финальный код. Код с пропущенной проверкой часто выглядит нормально и спокойно проходит все тесты. Модель-проверяющий без подсказки тоже не знает, куда смотреть: репозиторий огромный, функций сотни.
Сильная сторона. У агента осталась история: что он менял и что говорил по ходу. Его объяснения содержат допущения: «вход уже проверен», «вызывающий код надёжен». Каждое такое допущение превращается в конкретную задачу для проверки: открыть валидатор и посмотреть, кто вызывает функцию. Модель хорошо читает код и сверяет его с утверждением, когда утверждение сформулировано явно. Сама по себе она слабее ищет «что тут вообще может быть не так».
Как метод это использует. Он ограничивает область поиска изменёнными функциями (нужная функция попадает в область ревью почти всегда, а не в 13% случаев). Он превращает слова агента в проверяемые обязательства. А граничный тест, хранящийся отдельно, запоминает причину ограничения, которую обычные тесты не фиксируют.
Рычаги управления: - Какие данные давать проверяющему. Лучше всего сработали объяснения агента (вариант C2). Добавление истории изменений функции (дерево разбора, AST) ухудшило результат на обеих моделях. Больше контекста не значит лучше. - Число попыток. В исследовании одна попытка после провала проверки. Больше попыток дороже; меньше может оставить без шанса исправиться. - Ворота ремонта. Можно ужесточить: требовать запуска граничного теста до принятия патча, а не только после. - Кто владеет граничным тестом. Он хранится отдельно, чтобы агент не мог переписать его вместе с кодом.
Шаблон промпта
В статье нет готового текста промпта, есть только описание процесса. Оригинальный скил лежит в репозитории авторов (ссылка в ресурсах). Шаблон ниже — реконструкция по описанию процесса, а не копия оригинала. Структуру я сохранил: журнал решений, «ворота» ремонта, заметка с инвариантом, ворота обслуживания.
Ты ревьюер безопасности кода, который написал агент. Твоя задача — найти
пропущенные проверки и защитить починки от будущих правок.
Изменённые функции: {список_функций}
Задача агента: {описание_задачи}
Объяснения агента из лога (только текст, без кода): {объяснения_агента}
Предупреждения сканера (если есть): {предупреждения_сканера}
Текущий код функций: {код}
Для каждой изменённой функции:
1. Выпиши утверждения агента о входах, вызывающем коде, библиотеках.
2. Каждое утверждение — гипотеза. Составь план проверки: что прочитать.
3. Проверь по ТЕКУЩЕМУ коду (не по объяснению!). Вердикт:
подтверждено / опровергнуто / недостаточно данных.
4. Если данных мало — предложи запуск проверочного кода на обычных
и граничных входах.
5. Внеси запись в журнал решений:
{"гипотеза": ..., "функция": ..., "граница_входа": ..., "подозреваемый_изъян": ..., "вердикт": ...}
Принимай патч, только если выполнены ВСЕ условия:
- оценка изъяна подтверждена текущим кодом;
- проверены все функции, которые вызывают исправляемую;
- функциональные тесты проходят.
Иначе оставь код без изменений и объясни, чего не хватило.
После принятого патча создай заметку:
- инвариант: {что_должно_оставаться_верным}
- причина: {почему_ограничение_нужно}
- граничный_тест: исполняемая проверка, которая падает, если инвариант нарушен.
Храни тест отдельно от основного кода.
При любой следующей правке этих функций:
1. Прогони обычные тесты, тесты новой фичи и граничный тест.
2. Если граничный тест упал — покажи упавшую проверку и дай ОДНУ попытку
переделать реализацию, не ослабляя инвариант.
3. Если не вышло — оставь предыдущий защищённый код и сообщи об этом.
Что подставлять: список функций из диффа, текст задачи, дословные объяснения агента (из лога без блоков кода), результаты сканера при наличии и сам код. Ключевое поле — объяснения агента: без них метод теряет главное.
🚀 Быстрый старт — вставь в чат:
Вот шаблон TRACER для ревью кода агента. Адаптируй под мою задачу: [твоя задача].
Задавай вопросы, чтобы заполнить поля.
[вставить шаблон выше]
LLM спросит, какие функции изменил агент, где лежат его объяснения, что делает функция и кто её вызывает. Без этого она не сможет проверить утверждения агента по коду. Дальше она сама сформулирует инвариант и граничный тест под твой проект.
Ограничения
⚠️ Эффект на ревью небольшой: Лучшая конфигурация (код + объяснения агента) дала на основном тесте около 5 дополнительных починок на 345 патчей. Доверительный интервал включает ноль. Авторы сами пишут: это устойчивый порядок на двух моделях, а не большой прирост.
⚠️ Больше контекста — хуже: Добавление истории изменений функции поверх объяснений агента снизило число починок на обеих моделях. Не заваливай проверяющего всем подряд. Механизм авторы не установили: «разбавление контекста» — только гипотеза.
⚠️ Объяснения агента ненадёжны: Они не отражают реальные рассуждения модели. Это только гипотезы, их нельзя принимать на веру, и метод строится на их проверке.
⚠️ Нужен доступ к логу агента: Без объяснений и истории правок метод теряет главную идею. Для кода, написанного людьми, он не подходит.
⚠️ Для уже защищённого кода выигрыша нет: Там, где защита уже была, объяснения агента почти ничего не добавили (0 и 2 починки из 110).
⚠️ Многое теряется по дороге: До подтверждённой оценки у одной из моделей доходит лишь малая часть случаев (15–32 из 387). Я не вижу в тексте полного разбора причин, он обрезан. Метод не ловит всё.
⚠️ Узкая проверка: Эксперименты только на Python-проектах. Второе исследование (память о починках) — 5 компонентов и 10 задач, выборка маленькая. Один тип защиты (граничный тест) не покрывает все виды уязвимостей.
Как исследовали
Исследователи взяли публичный бенчмарк SusVibes: 186 задач в реальных Python-репозиториях, где агент пишет код, а отдельно проверяются функциональность и безопасность. Из 1302 патчей семи агентов-«авторов» (Claude 4 Sonnet, GLM-5, Kimi K2 и других) после отбора осталось 387. Две модели-ревьюера, DeepSeek-V3.2 и Gemini-3.8-Flash, пытались починить уязвимости в этих патчах в четырёх конфигурациях. В базовой видели код и предупреждения сканера. Остальные добавляли историю изменений функции, объяснения агента или и то и другое. Всего вышло 3096 запусков. Скрытые тесты безопасности служили только для оценки и ревьюерам не показывались.
Сначала смотрели, попадает ли нужная функция в область ревью: отслеживание изменённых функций дало 94,6% против 13,4% у основного предупреждения сканера. Затем считали проверенные починки. Объяснения агента дали лучший результат на обеих моделях: 76 и 75 прошедших тестов безопасности против исходных 68. Добавление AST-истории результат снижало. Функциональные регрессии были редкими, максимум один случай. Сравнение с базовой конфигурацией нечистое: C1–C3 получают ещё и «подсказки аудита», так что сравниваются пакеты данных, а не отдельные каналы.
Второй эксперимент проверял память. Пять компонентов софта для структурной биологии (индустриальный партнёр Nostrum Biodiscovery) сначала чинили, потом прогоняли через 10 задач на добавление фич. Схема 2×2: есть или нет заметка о починке, есть или нет граничный тест. Только с обратной связью от функциональных тестов получилось 4 из 10 задач и 3 нарушения защиты. С заметкой и граничным тестом получилось 9 из 10 и ни одного нарушения. Удивляет то, что функциональные тесты сами подталкивают модель к ослаблению защиты: ради «совместимости» она вернула weights_only=False, и все тесты прошли.
Адаптации и экстраполяции
🔧 Техника: добавить «граничный тест» в файл инструкций агента → защита от откатов
В CLAUDE.md или AGENTS.md:
Перед правкой любого файла из списка защищённых ({список_файлов}) прочитай SECURITY_NOTES.md. Для каждого инварианта оттуда запусти его проверку после правки. Если проверка падает — не ослабляй инвариант, ищи другой способ.Это моя адаптация идеи заметки с инвариантом, в статье такого файла нет. Заметки лежат в обычном файле, агент читает их при каждой правке. Проверьте на своём проекте.
🔧 Экстраполяция: ревью собственного PR агента перед отправкой
Прежде чем отправить PR, выпиши каждое своё утверждение о входных данных («это уже валидируется», «вызывающий код надёжен»). Для каждого найди в коде место, где это гарантируется. Не нашёл — добавь проверку или явно отметь допущение в описании PR.Это перенос принципа «объяснение — гипотеза» на самопроверку агента. В статье проверку делает отдельный ревьюер, не сам автор, и самопроверку авторы не тестировали.
Ресурсы
- Статья: Securing Agent-Generated Code with Its Own Development History
- Авторы: Prateek Kumar Rajput, Shahzeb Khan, El-Hacen Diallo, Yewei Song, Jacques Klein, Tegawendé F. Bissyandé. Университет Люксембурга, Nostrum Biodiscovery S.L. (Барселона).
- Скил TRACER: https://github.com/pkrajput/TRACER_skill
- Бенчмарк: SusVibes (оценка функциональности и безопасности отдельными наборами тестов)
- Связанное: ReAct, SWE-agent, CodeQL, Semgrep, GumTree, PAGENT
