3,583 papers
arXiv:2610.33450 78 24 окт. 2026 г. FREE

SecGent: безопасный код без потери функциональности через тесты до кода и «ворота» при исправлениях

КЛЮЧЕВАЯ СУТЬ
29,2% кода, который проходит обычные тесты, всё равно небезопасен. Метод SecGent позволяет получать код, который и работает, и не пускает атаки, без «перестраховки», когда модель из страха режет нужные входные данные. Фишка: сначала попроси модель написать уязвимую версию функции. По этому черновику находятся подходящие записи из каталога типовых уязвимостей (CWE). Затем отдельный тестировщик пишет два теста ещё до появления кода: на работу и на защиту. Эти тесты замораживаются, и кодер подгоняет решение под обе цели сразу.
Адаптировать под запрос
⚡

TL;DR

SecGent — это схема работы агента с кодом из пяти этапов. Сначала модель придумывает, как эту задачу можно реализовать уязвимо, и по этому описанию находит подходящие записи из каталога уязвимостей CWE. Затем отдельный «тестировщик» до написания кода пишет два теста: функциональный (нормальные данные дают нужный результат) и безопасностный (атакующие данные не дают вредного эффекта). Потом «кодер» пишет реализацию, прогоняет тесты и исправляет ошибки. Исправление принимается, только если одна из оценок выросла, а другая не упала.

Главная находка: код, который проходит обычные тесты, часто небезопасен. Почти треть работающих реализаций (29,2%) проваливает проверку безопасности. Если просто добавить в промпт информацию об уязвимостях, модель начинает «перестраховываться»: безопасность растёт, а нужное поведение ломается. В примере из статьи модель поставила жёсткую проверку на язык в заголовке и стала отвергать нормальные значения вроде en-US,en;q=0.9. Получается «безопасный», но бесполезный код.

Метод лечит это двумя ходами. Знание об уязвимостях подаётся как «возможные риски», а не как повод менять требуемое поведение. Замороженные заранее тесты с двумя целями не дают исправлению улучшить безопасность за счёт функциональности. Итог: на пяти языках и пяти моделях SecGent даёт лучшую совместную долю «работает и безопасно» среди всех сравниваемых подходов.

🔬

Схема метода

ШАГ 0 (один раз, заранее): подготовить каталог уязвимостей CWE
        → каждая запись переписана «языком кода»: ввод, операция, последствие

ШАГ 1: модель описывает, как задачу можно реализовать уязвимо
        → черновик уязвимого кода + 1-2 предложения «чего не хватает»

ШАГ 2: по черновику и ТЗ найти и отобрать 5 самых подходящих записей CWE
        → «контекст слабостей» (текст, без примеров кода)

ШАГ 3: агент-тестировщик (кода ещё нет, кода он не видит)
        → 1 функциональный тест + 1 безопасностный тест (привязан к CWE)
        → тесты проверены и ЗАМОРОЖЕНЫ

ШАГ 4: агент-кодер (тестов не видит) пишет реализацию
        → риски CWE = «возможные проблемы», не повод менять поведение

ШАГ 5: прогон тестов → оценка (функц., безопас.), например (1, 0)

ШАГ 6: ремонт (ограниченное число попыток), кодер видит код и ошибки
        → правка принимается, только если одна оценка выросла, а другая не упала

Шаги 1–2 в статье автоматизированы поиском по базе. Шаги 3–6 — это роли и порядок работы, их можно задать инструкцией агенту.

🚀

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

Задача: Вы делаете бэкенд для сервиса доставки еды. Клиент скачивает PDF-чек по имени файла. Нужна функция, которая отдаёт файл из папки /var/receipts. При этом должны работать и вложенные пути вида 2025/03/order_48213.pdf. Если вы просто попросите «сделай безопасно», модель может запретить все слэши и сломать выдачу чеков.

Промпт (для Claude Code или другого агента, который умеет запускать тесты):


Ты ведёшь разработку в три роли: Аналитик рисков, Тестировщик, Кодер.
Выполняй этапы строго по порядку.



Python-функция get_receipt(path_param: str) -> bytes.
Читает PDF-чек из папки /var/receipts по относительному пути от клиента.
Допустимые пути: order_48213.pdf и 2025/03/order_48213.pdf.
Если файла нет — вернуть None. Сигнатуру менять нельзя.



Напиши ГИПОТЕТИЧЕСКУЮ УЯЗВИМУЮ реализацию этой функции
и 1-2 предложения: какая операция или какая пропущенная проверка делает её опасной.
Гипотеза может быть неточной — это нужно для поиска рисков.
Затем назови до 5 типов слабостей (CWE), которые к ней относятся.



Кода ещё нет. Ты его не видишь.
Напиши ровно два теста:
- test_functional: валидные пути из задачи дают правильный результат.
- test_security: атакующий ввод не даёт вредного эффекта.
  Привяжи его к одной CWE из списка этапа 1.
Сохрани тесты в файл и больше не меняй их.



Напиши реализацию. Тесты не открывай.
Сохрани сигнатуру, поведение на валидных путях, возвращаемые значения.
Риски из этапа 1 — это ВОЗМОЖНЫЕ проблемы, а не повод менять требуемое поведение.



Запусти тесты. Запиши оценку: (функциональный, безопасностный), где 1 = прошёл, 0 = упал.



While оценка != (1,1) and попыток < 3:
    Предложи исправление, учитывая текст ошибки.
    Сохрани интерфейс и то, что уже проходило.
    Запусти тесты на предложении.
    If одна оценка выросла и другая не упала: принять исправление.
    Else: отклонить, оставить прежнюю версию.
В конце покажи: итоговый код, оценку и что отклонено.

Результат: Агент покажет гипотезу об уязвимой версии (скорее всего, склейка пути без проверки выхода за папку) и список CWE. Затем появятся два теста. Далее будут код, оценка прогона, например (1, 0), и один-два цикла исправлений. В конце вы получите функцию, которая отдаёт вложенные чеки и не даёт выйти за пределы папки. Рядом будет журнал: какие правки приняты, какие отклонены.

🧠

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

Слабость модели. Когда просишь «пиши безопасно», модель угадывает, чего бояться. Либо пропускает риск, который не очевиден из ТЗ: в задаче про заголовок нет слов «перевод строки» и «response splitting». Либо видит предупреждение и перестраховывается: добавляет лишние проверки и ломает нужное поведение. Безопасность и функциональность тянут в разные стороны, и без второй цели модель жертвует ею.

Сильная сторона. Модель хорошо умеет придумывать, как можно сделать плохо, а также писать тесты и чинить код по тексту ошибки. В статье именно описание уязвимой реализации помогло найти нужную слабость. Поиск по сухому ТЗ нашёл правильные CWE у 47,1% задач, а через «гипотетический уязвимый код» — у 89,1%.

Как метод это использует. Он переводит ТЗ на «язык уязвимостей» и фиксирует обе цели тестами до написания кода. Кодер не видит тестов и не подгоняет решение под них. Правило «одна оценка выросла, другая не упала» не даёт ремонту обменять безопасность на функциональность. В статье именно ремонт по обратной связи чаще всего возвращал потерянное поведение и при этом сохранял безопасность.

Рычаги управления: - Число попыток ремонта → меньше для простых функций, экономия времени; больше для сложных. - Число CWE в контексте (в статье 5) → меньше даёт точность, больше даёт охват. - Условие принятия правки → можно ужесточить: «сначала обе оценки должны быть не хуже, а общая сумма выше». - Один тест на цель → если нужны более широкие проверки, разреши несколько проверок внутри одного теста, как в статье. - Разделение чатов → тестировщик и кодер в отдельных диалогах или субагентах: так кодер действительно не видит тестов.

📋

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


Ты ведёшь разработку в три роли: Аналитик рисков, Тестировщик, Кодер.
Выполняй этапы строго по порядку.



Язык: {язык}
Что сделать: {описание_функции}
Сигнатура (менять нельзя): {сигнатура}
Допустимые входные данные (должны продолжать работать): {валидные_примеры}
Ошибки и крайние случаи: {поведение_при_ошибке}



Напиши ГИПОТЕТИЧЕСКУЮ УЯЗВИМУЮ реализацию функции
и 1-2 предложения: какая операция или пропущенная проверка делает её опасной.
Гипотеза может быть неточной.
Назови до {число_CWE} типов слабостей (CWE), которые к ней относятся.



Кода ещё нет. Ты его не видишь.
Напиши ровно два теста на {тестовый_фреймворк}:
- test_functional: валидные входные данные дают ожидаемый результат.
- test_security: атакующий ввод не даёт вредного эффекта; привяжи к одной CWE из этапа 1.
Сохрани и не меняй.



Напиши реализацию. Тесты не открывай.
Сохрани сигнатуру, поведение на валидных данных, возвращаемые значения и побочные эффекты.
Риски из этапа 1 — возможные проблемы, а не повод менять требуемое поведение.



Запусти тесты командой {команда_запуска}.
Запиши оценку (функциональный, безопасностный): 1 = прошёл, 0 = упал.



While оценка != (1,1) and попыток < {число_попыток}:
    Предложи исправление по тексту ошибки.
    Сохрани интерфейс и то, что уже проходило.
    Запусти тесты на предложении.
    If одна оценка выросла и другая не упала: принять.
    Else: отклонить, оставить прежнюю версию.
Покажи: итоговый код, оценку, список принятых и отклонённых правок.

Подставьте язык, описание функции, сигнатуру и 2-3 примера допустимых данных. Чем точнее вы опишете допустимый ввод, тем меньше шансов, что модель его «зарежет».

🚀 Быстрый старт — вставь в чат или в сессию агента:

Вот шаблон SecGent (безопасный код через тесты до кода). Адаптируй под мою задачу: [твоя задача].
Задавай вопросы, чтобы заполнить поля.

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

LLM спросит про язык, сигнатуру, примеры допустимых данных и команду запуска тестов. Допустимые данные нужны, чтобы тестировщик написал функциональный тест, а кодер не сломал нужное поведение. Команда запуска нужна, чтобы агент мог проверить код сам.

⚠️

Ограничения

⚠️ Нужен запуск кода: без запуска тестов (Claude Code, Cursor, любой агент с терминалом) схема превращается в «подумай ещё раз». Эффект от обратной связи держится на реальных прогонах.

⚠️ Оригинал — это конвейер на коде: поиск по каталогу CWE, ранжирование и контейнеры для тестов собраны программно. Шаблон выше — упрощённый перенос идей через инструкцию. Модель сама назовёт CWE по памяти, а в статье записи подбирались поиском. Такой перенос авторы не проверяли.

⚠️ Изоляция ролей: в одном диалоге кодер видит тесты и может подгонять код под них. В статье тестировщик и кодер были раздельными агентами. Для честной проверки разнесите их по отдельным чатам или субагентам.

⚠️ Проверяются только то, что покрыто тестами: прохождение двух тестов не доказывает безопасность. Каждый тест проверяет одну выбранную слабость. Другие уязвимости остаются непокрытыми.

⚠️ Один бенчмарк: результаты получены на наборе CWEval (119 коротких функций). Для больших проектов с зависимостями и несколькими файлами результат не проверялся.

⚠️ Тесты могут оказаться плохими: если сгенерированный тест пропускает опасный ввод или проверяет не то, исправление «пройдёт» без пользы. В статье такие наборы помечаются непригодными и не запускают ремонт.

🔍

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

Авторы взяли CWEval — набор из 119 задач, где у каждой есть два независимых проверяющих теста: на функциональность и на безопасность. Метрика FS@k — вероятность, что среди k вариантов хотя бы один проходит обе проверки. Это важно: многие приёмы улучшали безопасность только потому, что убирали нужное поведение, а по раздельным метрикам такого не видно.

Сравнивали SecGent с четырьмя подходами на этапе генерации: обычным запросом, RAG с записями CWE, автоматическими оптимизаторами промптов (MIPROv2, GEPA) и AutoGen. Тестировали четыре открытые модели (например, Qwen3-Coder-30B) и Claude Sonnet 5.5 на пяти языках (Python, C, C++, Go, JavaScript). Отдельно сравнили с Claude Code с промптом про безопасность.

Результаты. SecGent выигрывает у всех на всех моделях. На открытых моделях средний FS@1 вырос на 15,5% относительно RAG и на 19,8% относительно лучшего оптимизатора промптов. На Sonnet FS@1 дошёл до 92,1%, это на 4,8% выше AutoGen. Claude Code с промптом про безопасность уступил на 6,2%. Обратная связь от тестов добавила до 6,7% по сравнению с генерацией без неё.

Удивило, откуда берётся прирост от обратной связи. Он пришёл в основном из возвращения потерянной функциональности при сохранении безопасности, а не из дополнительной «закалки» кода. Для практики это значит, что ремонт без двух целей почти бесполезен: безопасный, но неработающий код легко получить, а вот безопасный и работающий — трудно. Ещё один сюрприз: даже обычный RAG с CWE улучшает безопасность и одновременно снижает функциональность. У CodeGuarder (поиск с примерами уязвимого и исправленного кода) потери функциональности в основном съели выигрыш по безопасности.

Чтобы тесты не «подтекали» в оценку, авторы не использовали вердикты CWEval для ремонта. Ремонт шёл только по тестам, которые написал сам агент.

💡

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

🔧 Техника: добавить правило в CLAUDE.md / AGENTS.md → агент всегда проверяет обе цели

Если не хочется вставлять большой шаблон каждый раз, оставьте в файле инструкций короткий вариант:

При написании кода, который принимает внешний ввод (HTTP, файлы, БД, логи, команды):
1. Сначала напиши, как эту функцию можно реализовать уязвимо, и назови 2-3 релевантные CWE.
2. Напиши два теста: функциональный (валидный ввод) и безопасностный (атакующий ввод). Не меняй их при исправлениях.
3. Риски — это «возможные проблемы», а не повод менять требуемое поведение и сигнатуру.
4. Принимай исправление, только если одна оценка выросла, а другая не упала.

Это перенос идей статьи в файл инструкций, а не измеренная в ней конфигурация. Работает как стартовая точка, дальше проверяйте на своих задачах.

🔗

Ресурсы

  • Secure and Functional Code Generation with Self-Evaluation — Md Afif Al Mamun (University of Calgary, Канада), Gias Uddin (York University, Канада), 2026.
  • Бенчмарк: CWEval (проверка функциональности и безопасности исполняемыми тестами).
  • Источник знаний об уязвимостях: MITRE CWE (представление CWE-1000, 944 слабости).
  • Метод поиска: HyDE (Hypothetical Document Embeddings).
  • Сравнивались: RAG, MIPROv2, GEPA, AutoGen, CodeGuarder, RESCUE, Claude Code.

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

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

29,2% кода, который проходит обычные тесты, всё равно небезопасен. Метод SecGent позволяет получать код, который и работает, и не пускает атаки, без «перестраховки», когда модель из страха режет нужные входные данные. Фишка: сначала попроси модель написать уязвимую версию функции. По этому черновику находятся подходящие записи из каталога типовых уязвимостей (CWE). Затем отдельный тестировщик пишет два теста ещё до появления кода: на работу и на защиту. Эти тесты замораживаются, и кодер подгоняет решение под обе цели сразу.

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

Пять этапов, как на конвейере. Каждая роль делает своё и не лезет в чужое. 1. Аналитик пишет уязвимый черновик и называет до 5 типов слабостей. 2. Тестировщик не видит кода. Он пишет функциональный и безопасностный тесты и замораживает их. 3. Кодер не видит тестов. Риски для него «возможные проблемы», а не повод менять поведение. 4. Прогон даёт оценку вида (1, 0): работает, но не защищено. 5. Ремонт идёт ограниченное число попыток. Правка принимается, только если одна оценка выросла, а другая не упала. Это «ворота» против обмена безопасности на функциональность. Как турникет в метро: пропускает в одну сторону и не даёт откатиться.

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

Просишь «пиши безопасно» — модель гадает, чего бояться. Либо пропускает риск, которого нет в задании. Либо пугается и добавляет лишние проверки. В примере из статьи она жёстко проверила язык в заголовке и стала отвергать нормальное значение en-US,en;q=0.9. Код «безопасный», но бесполезный. Модель хорошо придумывает, как сделать плохо. Поиск уязвимостей по сухому заданию нашёл нужные записи только у 47,1% задач. Поиск по «гипотетическому уязвимому коду» нашёл у 89,1%. Чтобы найти защиту, надо сначала описать атаку. Дальше работают замороженные тесты. Кодер их не видит и не подгоняет решение. Ворота не дают ремонту улучшить одну цель за счёт другой. На пяти языках и пяти моделях метод дал лучшую долю «работает и безопасно» среди всех сравниваемых подходов.

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

Генерация кода агентом → особенно функции, которые принимают данные снаружи: пути к файлам, заголовки, запросы к базе, разбор ввода. Подходит, когда фраза «сделай безопасно» уже ломала нужное поведение. Не подходит, если нет запуска кода: без терминала схема превращается в «подумай ещё раз». Для больших проектов на много файлов метод не проверяли: тестировали 119 коротких функций.

Мини-рецепт

1. Опиши задачу жёстко: сигнатуру, 2-3 примера допустимых данных и поведение при ошибке. Чем точнее допустимый ввод, тем меньше шансов, что его «зарежут».
2. Разведи роли: тестировщик и кодер в разных чатах или субагентах. В одном диалоге кодер подсмотрит тесты.
3. Попроси уязвимую версию: <роль>Аналитик рисков пишет плохой код, 1-2 предложения «чего не хватает» и до 5 типов слабостей (CWE).
4. Закажи ровно два теста до кода: один на валидные данные, второй на атаку, привязанный к одной слабости. Сохрани файл и запрети менять.
5. Скажи кодеру главное: риски — это возможные проблемы, а не повод менять поведение.
6. Запусти тесты и запиши оценку: (функциональный, безопасностный), где 1 — прошёл, 0 — упал.
7. Включи ворота ремонта: до 3 попыток. Принимай правку, только если одна оценка выросла, а другая не упала. Остальное откатывай.
8. Требуй журнал: итоговый код, оценка, что принято, что отклонено.

Примеры

[ПЛОХО] : Напиши функцию, которая отдаёт PDF-чек по пути от клиента. Сделай максимально безопасно. (Модель запретит все слэши и сломает пути вида 2025/03/order_48213.pdf.)
[ХОРОШО] : Ты ведёшь разработку в три роли: Аналитик рисков, Тестировщик, Кодер. Задача: Python-функция get_receipt(path_param: str) -> bytes, читает PDF из /var/receipts. Допустимы order_48213.pdf и 2025/03/order_48213.pdf, если файла нет — вернуть None, сигнатуру менять нельзя. Этап 1: напиши гипотетическую уязвимую реализацию и назови до 5 типов слабостей (CWE). Этап 2: не видя кода, напиши ровно два теста — на валидные пути и на выход за папку, — сохрани и не меняй. Этап 3: напиши код, тесты не открывай, риски — это возможные проблемы, а не повод менять поведение. Этап 4: запусти тесты, запиши оценку (функциональный, безопасностный). Этап 5: до 3 попыток ремонта, принимай правку, только если одна оценка выросла, а другая не упала. В конце покажи код, оценку и отклонённые правки. [ХОРОШО, быстрый старт]: Вот шаблон SecGent. Адаптируй под мою задачу: [твоя задача]. Задавай вопросы про язык, сигнатуру, допустимые данные и команду запуска тестов.
Источник: Secure and Functional Code Generation with Self-Evaluation
ArXiv ID: 2610.33450 | Сгенерировано: 2026-10-29 06:50

Проблемы LLM

ПроблемаСутьКак обойти
Добавил в запрос вторую цель, и модель жертвует первойПросишь «пиши безопасно» или даёшь список угроз. Модель перестраховывается. Добавляет лишние проверки и режет нормальные входные данные. Например, запрещает все слэши в пути и ломает вложенные папки. Безопасность растёт, нужное поведение пропадает. Так бывает с любой парой целей: безопасность и скорость, строгость и полнота, стиль и точность. Главную цель модель отпускает первойНе давай предупреждение как повод менять поведение. Пиши: «Это возможные риски. Требуемое поведение на допустимых данных менять нельзя». Приложи 2–3 примера данных, которые обязаны работать. Проверяй обе цели отдельными тестами (см. методы)

Методы

МетодСуть
Две замороженные проверки до работы и ворота на правки — цели не обменяются друг на другаСначала составь проверки, потом делай работу. Нужны две: «нормальный ввод даёт нужный результат» и «плохой ввод не даёт вреда». Сохрани их и не меняй. Исполнителю их не показывай. Тогда он не подгонит решение под тест. После прогона запиши оценку: (функц., безопас.), например (1, 0). Любую правку принимай только по правилу: одна оценка выросла, другая не упала. Иначе откат к прошлой версии. Почему работает: исправление не может улучшить одну цель за счёт другой. Проверки составлены до решения и не зависят от него. Тест видит не то, что хотел бы автор кода. Когда да: любые задачи с двумя целями и возможностью запустить проверку. Это код, а также данные, SQL, конфиги. Когда нет: нет запуска. Тогда схема превращается в «подумай ещё раз». Нужны отдельные чаты или субагенты для тестировщика и кодера. Иначе кодер видит тесты. Два теста не доказывают полную безопасность. Они покрывают только выбранную слабость
Сначала опиши, как задачу сделать плохо, — находишь скрытые рискиПеред работой попроси: «Напиши гипотетическую уязвимую версию и 1–2 предложения, какая пропущенная проверка делает её опасной». Затем: «Назови до 5 типов слабостей, которые к ней относятся». Гипотеза может быть неточной. Она нужна как приманка для поиска. Почему работает: модель хорошо придумывает, как сломать. Но плохо угадывает, чего бояться, по сухому описанию задачи. В ТЗ часто нет слов про опасность. Например, про заголовок не сказано «перевод строки». Пример плохой версии выводит эти слова наружу. Поиск рисков по такому описанию находит нужные слабости почти вдвое чаще, чем по ТЗ. Когда да: безопасность, надёжность, крайние случаи, разбор рисков в любой области. Когда нет: задача тривиальна. Или ты уже знаешь список рисков
📖 Простыми словами

Secure and Functional Code Generation with Self-Evaluation

arXiv: 2610.33450

Просить нейросеть «написать безопасный код» — бесполезная затея. Модель либо прохлопает скрытую дыру, либо перестрахуется так, что сломает половину функций. Фреймворк SecGent лечит это разделением труда: модель сначала специально придумывает, как задачу можно взломать, сверяется с каталогом уязвимостей CWE, и только потом думает о защите.

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

Что реально работает в SecGent: подход Test-Driven Development на стероидах. До написания кода агент создаёт два барьера — функциональный тест на нормальную работу и атакующий тест со зловредными данными. Затем кодер выдаёт решение и полирует его по жесткому правилу: правка принимается, только если безопасность выросла, а функционал не пошёл по бороде.

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

Короче: хватит надеяться на промпт «напиши без багов». Безопасность кода — это не вера в интеллект сетки, а жесткий арбитраж двух целей. Заставляй модель сначала думать как хакер, писать тесты на поломку, а потом проверять саму себя. Кто строит пайплайны через взаимный контроль тестов, получает рабочий прод, остальные — бесконечно чинят самострелы защиты.

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

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

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