3,583 papers
arXiv:2608.09740 70 10 авг. 2026 г. FREE

Тесты как спецификация: почему код, прошедший все проверки, всё равно может быть дырявым

КЛЮЧЕВАЯ СУТЬ
Парадокс: код, который проходит все показанные тесты, всё равно может быть насквозь дырявым. Метод SecTDD позволяет получать от LLM код, который сначала проверяют на конкретных вход-выход примерах — вместо смутного 'напиши безопасно'. Модель не понимает суть задачи — она подстраивается под то, что видит. Итог: код проходит показанные тесты, но ломается на скрытых с другими граничными значениями.
Адаптировать под запрос

TL;DR

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

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

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


🔬

Схема метода

ШАГ 1: Дать модели конкретные тест-кейсы и примеры атак вместо общего "сделай безопасно" 
       → код, заточенный именно под эти случаи

ШАГ 2: Прогнать код на реальных данных, найти провал
       → точный текст ошибки/лога

ШАГ 3: Вернуть модели этот текст ошибки, попросить переписать код ЦЕЛИКОМ
       → новая версия кода

ШАГ 4: Повторить шаги 2-3 максимум 1-2 раза
       → финальный код

ШАГ 5 (обязательно): Проверить код на кейсах, которые НЕ входили в исходные тесты
       → страховка от ложного чувства безопасности

Все шаги — в одном диалоге, отдельные сообщения подряд.


🚀

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

Задача: Маркетолог просит у ChatGPT скрипт на Python для формы на сайте (Tilda, Taplink, самописный лендинг) — форма принимает файл от пользователя (резюме, фото, документ) и сохраняет его на сервер.

Промпт (шаг 1):

Напиши функцию на Python, которая сохраняет файл, загруженный пользователем через веб-форму, 
на диск в папку /uploads/. Функция должна проходить такие проверки:

1. Обычный файл "photo.jpg" — сохраняется нормально.
2. Если имя файла "../../etc/passwd" или "../../../config.py" — функция должна ОТКАЗАТЬ 
   и не сохранять файл (защита от выхода за пределы папки).
3. Если в имени файла есть спецсимволы, нулевые байты или двойное кодирование пути — 
   функция должна отказать.
4. Размер файла больше 10 МБ — отказ.

Покажи код и объясни, как он проверяет каждый пункт.

Промпт (шаг 2, если нашёл дырку):

Я протестировал твою функцию: подсунул имя файла "..%2f..%2fetc%2fpasswd" (закодированный путь), 
и файл сохранился ВНЕ папки /uploads/. Вот что вернула функция: [вставить сообщение об ошибке или лог].

Перепиши функцию ЦЕЛИКОМ с защитой от этого случая тоже.

Результат: Модель выдаст переписанную функцию с явной проверкой и очисткой пути к файлу (нормализация пути, проверка что итоговый путь остаётся внутри /uploads/). После этого стоит попробовать ещё пару похожих трюков (абсолютный путь, символическую ссылку, unicode-обход) — потому что прохождение первых тестов не гарантирует, что закрыты все похожие дырки.


🧠

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

Модель, получив общую инструкцию "будь безопасен", не знает, что конкретно проверять — она добавляет общие практики "на глаз", но может не подумать именно про твой конкретный вектор атаки. Абстрактная безопасность для модели — не счётчик, который можно проверить, а размытое пожелание.

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

Метод превращает расплывчатое "сделай безопасно" в конкретную, проверяемую задачу: покажи тест — получи защиту от него, покажи ошибку — получи исправление именно этой ошибки. Но это же и ограничение: закрыто только то, что показано явно.

Рычаги управления: - Количество тест-кейсов заранее → больше конкретных примеров атак = более специфичная защита, но никогда не полная. - Формат фидбека (полный лог vs короткая выдержка) → роли почти не играет, можно смело копировать весь текст ошибки без причёсывания. - Число раундов исправления → для простых задач достаточно одного раунда, не нужно гонять цикл до бесконечности.


⚠️

Ограничения

⚠️ Ложное чувство безопасности: код, прошедший все твои проверки, может остаться дырявым в случаях, которые ты не тестировал. Тестирование закрывает только то, что явно показано модели.

⚠️ Общие напоминания непредсказуемы: фраза "напиши безопасный код" без конкретных примеров иногда помогает, а иногда портит результат — модель может как переусердствовать и сломать функциональность, так и вовсе проигнорировать угрозу.

⚠️ Формат фидбека почти не важен: не трать время на структурирование сообщений об ошибке для модели — простое копирование лога работает почти так же хорошо, как аккуратно оформленное сообщение.


🔗

Ресурсы

"Security Tests as Executable Specifications for LLM Code Generation: Benefits, Trade-offs, and Coverage Limits" — Yunhao Liang, Chengguang Gan, Ruixuan Ying, Hanjun Wei, Zhe Cui, Shiwen Ni (Chengdu Institute of Computer Applications CAS, Tohoku University, Shenzhen University of Advanced Technology). Используемые бенчмарки: CWEval, SALLM, CodeGuard+.


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

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

Парадокс: код, который проходит все показанные тесты, всё равно может быть насквозь дырявым. Метод SecTDD позволяет получать от LLM код, который сначала проверяют на конкретных вход-выход примерах — вместо смутного 'напиши безопасно'. Модель не понимает суть задачи — она подстраивается под то, что видит. Итог: код проходит показанные тесты, но ломается на скрытых с другими граничными значениями.

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

Не проси модель 'напиши безопасный код' — покажи ей конкретные пары вход-выход, включая граничные случаи. Это похоже на то, как учат ребёнка не абстрактным правилом 'будь аккуратен', а конкретным примером ошибки. Если код провалился — не пиши 'исправь ошибки', а верни точный проваленный вход и ожидаемый результат. Модель точнее реагирует на конкретный пример, чем на общую фразу — это её сильная сторона, работа по образцу (few-shot), а не по смыслу задачи.

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

LLM хорошо копирует паттерн из показанного примера, но плохо домысливает то, что не показали. Когда даёшь абстрактное требование 'проверяй SQL-инъекции', модель следует общим шаблонам из обучения и пропускает конкретные граничные случаи. Когда даёшь пример 'DROP TABLE users;-- → False', модель точно знает, что должно случиться с похожим вводом. Именно поэтому конкретный проваленный кейс в фидбеке работает лучше общего 'тут ошибка' — исследование прогнало 2705 траекторий и разница была стабильной. Но тут же кроется ловушка: избыток тестов заранее заставляет модель подгоняться именно под них, а не под суть задачи.

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

Генерация кода через LLM → конкретно для валидаторов, парсеров входных данных и любых функций, где важны граничные случаи (SQL-инъекции, кодировки, пустые строки, эмодзи в вводе). Особенно полезно когда нет возможности сразу написать формальную спецификацию, но легко придумать 5-10 примеров вход-выход. НЕ подходит как единственная проверка — прохождение показанных тестов не значит, что код безопасен на новых данных.

Мини-рецепт

1. Собери примеры: 5-10 пар вход-выход, включая явно странные случаи (пустая строка, кириллица, спецсимволы).
2. Дай задачу с примерами: не описывай требования словами — покажи validate_promo('DROP TABLE;--') → False прямо в промпте.
3. Прогони код на этих примерах: через встроенный код-интерпретатор LLM или вручную.
4. При провале — верни точный кейс: не 'исправь ошибки', а Вход: X, ожидалось: Y, получилось: Z. Перепиши функцию целиком.
5. Повтори максимум 2 раза: если после второго круга не проходит — пересмотри примеры, возможно они противоречат друг другу.
6. Проверь на НОВЫХ примерах: придумай тесты, которых модель не видела — это единственный способ узнать, понял код суть задачи или просто подстроился.

Примеры

[ПЛОХО] : Напиши функцию проверки промокода. Она должна быть безопасной и защищённой от инъекций.
[ХОРОШО] : Напиши validate_promo(code: str) -> bool. Примеры: validate_promo('SALE2024') → True validate_promo("DROP TABLE users;--") → False validate_promo('a') → False Проверь код на этих примерах перед ответом. После получения кода — придумай новый пример (промокод с эмодзи) и прогони отдельно. Если провалился: Вход: '🎉SALE', ожидалось: False, получилось: True. Перепиши функцию целиком с учётом этого случая.
Источник: Security Tests as Executable Specifications for LLM Code Generation: Benefits, Trade-offs, and Coverage Limits
ArXiv ID: 2608.09740 | Сгенерировано: 2026-08-11 05:50

Проблемы LLM

ПроблемаСутьКак обойти
Код проходит показанные тесты, но ломается на скрытых случаяхМодель подгоняет код под конкретные примеры, которые видит. Не понимает суть задачи целиком. Меняешь граничное значение, кодировку или формат входа — код ломается, хотя все показанные тесты прошёлПроверяй финальный код на новых примерах, которые модели не показывал. Если что-то провалилось — верни точный проваленный вход, а не общее "исправь ошибки"
Абстрактные напоминания о качестве работают непредсказуемоПросишь "пиши безопасно" или "будь внимателен к качеству" без конкретики. На одной модели это улучшает результат, на другой — портит. Эффект не предсказать заранееЗамени абстрактную фразу конкретными примерами вход ожидаемый результат. Включи граничные случаи прямо в промпт

Методы

МетодСуть
Тест-кейсы вместо словесного описания задачиДай модели конкретные примеры вход ожидаемый результат, включая граничные случаи, вместо общего описания требований. Модель точнее подгоняет логику под конкретику, чем под расплывчатые слова — это сильная сторона LLM (few-shot). Работает для любой генерации кода или структурированного контента, где есть чёткий критерий "правильно/неправильно". Не работает, если нет способа реально проверить результат выполнением
Точный проваленный пример вместо "исправь ошибки"Когда тест не прошёл — верни модели именно этот вход, что ожидалось и что получилось. Попроси переписать код целиком, а не патчить фрагмент. Конкретный провал направляет исправление точнее общей фразы об ошибке. Достаточно 1-2 повторов цикла для большинства задач, для сложных — до 2-3. Не работает без реального выполнения кода — на глаз ошибку не увидеть точно
📖 Простыми словами

Security Tests as Executable Specifications forLLMCode Generation: Benefits, Trade-offs, and Coverage Limits

arXiv: 2608.09740

Суть в том, что современные нейронки при написании кода ведут себя как талантливые, но дико невнимательные джуны. Когда ты просишь модель «сделай безопасно», для неё это пустой звук, просто вежливое пожелание. Исследователи выяснили, что единственный способ заставить LLM не плодить дыры в защите — это дать ей исполняемые спецификации, то есть конкретные тесты безопасности. Модель не понимает абстрактную «защищенность», но она отлично понимает, когда код должен пройти через жесткий фильтр проверок на взлом.

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

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

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

Короче, хватит надеяться на встроенную «совесть» и базовые настройки безопасности ChatGPT или Claude. Если код критически важен, нужно скармливать модели security-тесты и заставлять её переделывать решение, пока все проверки не станут зелеными. 10 из 15 уязвимостей закрываются именно так, а не через длинные промпты о вреде хакинга. Кто не тестирует на лету — тот гарантированно оставляет бэкдор для первого встречного школьника.

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

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

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