Примечание: изначально я хотел написать одну большую статью, которая закрыла бы всю методологию и технические детали приемов, которыми я пользуюсь: дифференциальный фаззинг на базе ИИ, grammar-based fuzzing, автоматическую генерацию harness-ов и связанные с этим рабочие процессы. Но я понял, что если упаковать все это в один текст, он станет непомерно плотным и тяжелым для чтения.

Поэтому перед вами первая часть. Здесь я даю верхнеуровневый обзор методологии и ключевых идей, на которых строится мой подход к использованию LLM. В следующих публикациях я подробнее разберу технические детали и реализацию каждого компонента.

Все, что описано ниже, предназначено строго для обучения и исследований. Любое неправильное использование или вредоносная активность на его основе остаются на совести того, кто этим занимается. 

Содержание

  1. Вступление

  2. Почему запрос «найди все уязвимости» не работает

  3. Минимальный каркас, который действительно помогает

  4. Кейс: Claude Opus 4.6 и Firefox

  5. Моя методология

  6. Золотая середина

  7. Prompt Injection

  8. Источники

Вступление

За последние несколько недель я отправил довольно много репортов об уязвимостях. Небольшая их часть уже исправлена и раскрыта через бюллетени безопасности. Все они найдены исключительно с помощью LLM, без какого-либо ручного ревью исходного кода. Проекты, в которых я нашел эти проблемы, хорошо известны и широко используются. Среди них есть известные вроде Parse Server, HonoJS, ElysiaJS, Harden Runner и еще около десятка заметных проектов.

На мой взгляд, это доказывает: агентные CLI/TUI-инструменты вроде OpenAI Codex без всяких сомнений могут помогать находить серьезные уязвимости. Но как именно использовать их так, чтобы выявлять неочевидные проблемы? По итогам экспериментов и тысяч и тысяч промптов, отправленных в попытках найти уязвимости, я пришел к нескольким выводам. Возможно, они небезупречны с теоретической точки зрения, но это самые практичные выводы, к которым я смог прийти. 

Я обнаружил, что проще всего пропустить важную уязвимость так:

  1. переусложнить аудит безопасности избыточным каркасом и цепочками промптов;

  2. раздуть файлы AGENT.md и SKILLS.md;

  3. засыпать модель контекстом — документами или подробным планированием каждого шага процесса;

  4. пытаться оркестрировать слишком многое одновременно.

Звучит контринтуитивно, верно? 

Здравомыслящий человек скажет: чем больше направляющих инструкций, тем лучше результат. Но у систем с длинным контекстом есть вполне реальная и хорошо изученная проблема. Чем больше токенов вы набиваете в окно контекста, тем хуже модель отбирает нужные детали. Недавние работы называют это context rot: качество становится все менее стабильным по мере роста длины контекста, даже когда добавленный контент формально релевантен. Аудит безопасности — едва ли не худшая среда для такой проблемы. «Иголка» здесь обычно представляет собой одно тонкое нарушение ожидаемого свойства системы, зарытое среди тысяч корректных строк. По моим наблюдениям, во многих случаях модели ведут себя в соответствии с принципом первичности и недавности: они лучше справляются, когда «иголка» находится в начале или конце контекста, и хуже — когда она спрятана в середине. Это и есть проблема иголки в стоге сена в ее чистом виде. 

Так что же делать? Выбросить AGENTS.md и пустить LLM свободно бродить по коду без всякого каркаса? Это ведет к еще более серьезным проблемам, но это тема для другой статьи. Пока что, опираясь на тесты и опыт поиска более чем десятка CVE в популярных опенсорс-проектах, я для себя зафиксировал такой подход: минимальный постоянный каркас, максимально точечное исследование и проверка, а также рабочий процесс, который удерживает внимание модели на главном. 

Почему запрос «найди все уязвимости» не работает

Допустим, у вас есть большая папка с монолитным исходным кодом или вы только что клонировали репозиторий с GitHub и хотите найти в нем уязвимости. Первое, что вы делаете: запускаете Codex и пишете промпт «найди все уязвимости в этой кодовой базе». Этот промпт не работает по двум конкретным причинам.

Когда вы написали такой промпт, вы не указали модель угроз. В результате у LLM нет понимания импакта от своих находок. Она может вывести какую-то модель угроз сама, но, как показывают мои эксперименты, обычно либо не делает этого вовсе, либо делает плохо. Без нормальной модели угроз, границ доверия, возможностей атакующего и предварительных условий вы получите длинный список общих CWE-подобных гипотез без приоритизации. У вас не будет способа отличить интересные находки от шума.

Вторая причина в том, что такой промпт подталкивает модель к breadth-first hallucination. Мы знаем, что широкие промпты провоцируют широкие ответы. Широкие промпты порождают широкие ответы. Модель начинает сопоставлять код с распространенными классами багов даже там, где они в вашем конкретном контексте невозможны. В итоге вы будете ревьюить теоретические уязвимости во фрагментах кода, до которых атакующий никогда не доберется.

Минимальный каркас, который действительно помогает

Раз мы поняли, к чему ведет расплывчатый промпт, давайте сделаем все правильно. Прежде чем просить LLM провести аудит кода на уязвимости, попробуйте сделать то, что делают ИБ-команды: определите модель угроз. Кстати, ее тоже можно сгенерировать с помощью LLM. Обычно я ищу ранее раскрытые CVE в проекте и на основе их описаний прошу LLM построить модель угроз для правдоподобных классов багов.

Например, если ранее раскрытые CVE были связаны с heap overflow, stack overflow, integer overflow и memory corruption, LLM попытается построить модель угроз вокруг таких уязвимостей, потому что их уже признали реальными проблемами.

Дальше берете только что созданный документ с моделью угроз, скармливаете его Codex и просите LLM поискать связанные правила безопасности. Или, например, найти коммиты, которые исправляли эти уязвимости, а затем поискать обходы этих исправлений. С большой вероятностью это принесет больше уязвимостей, чем расплывчатый промпт. 

В этом случае весь каркас сводится к созданию модели угроз. Никаких skills.md или agents.md вы не пишите, не пытаетесь ничего оркестрировать. Просто создали модель угроз, отдали ее LLM, и теперь модель углубленно исследует кодовую базу в поиске уязвимостей, которые попадают в эту модель. 

Когда вы закончите искать уязвимости из тех же категорий, что и ранее раскрытые CVE, попробуйте определить точки входа: HTTP-маршруты, RPC-обработчики, потребители сообщений, CLI-точки входа и запланированные задачи. Обозначьте границы доверия: браузер ↔ сервер, сервис ↔ сервис, плагин ↔ хост, песочница ↔ привилегированная часть. Выделите операции повышенного риска: десериализацию, шаблонизацию, нативные биндинги, проверки авторизации и парсинг недоверенных данных. И явно зафиксируйте модель «атакующий — жертва»: например, вас интересуют уязвимости, которые может вызвать удаленный неаутентифицированный пользователь, удаленный аутентифицированный пользователь с низкими правами или кросс-тенантный пользователь. 

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

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

Кейс: Claude Opus 4.6 и Firefox

В публикации Anthropic от 6 марта 2026 года описано сотрудничество с Mozilla, в ходе которого Claude Opus 4.6 нашел 22 уязвимости примерно за две недели, причем Mozilla оценила 14 из них как high severity. Собственный пост Mozilla подтверждает результат и подчеркивает, почему это сработало на практике. Отчеты приходили с минимальными тестовыми примерами, что ускоряло воспроизведение и исправление багов, а команда расширила технику за пределы JS-движка на другие части браузера.

Что на самом деле сделала Anthropic

Описание исследования Anthropic не сводится к тому, что «мы написали мега-промпт». Они начали с узкого фрагмента кодовой базы — движка JavaScript, поскольку он имеет критическое значение и поддается анализу в изолированном виде. Дальше команда работала короткими итерациями. 

Claude обнаружил уязвимость use-after-free примерно через 20 минут исследования, люди подтвердили находку, и команда отправила отчет в Bugzilla с патчем-кандидатом. Как только рабочий процесс доказал свою эффективность, его масштабировали. В итоге просканировали около 6000 файлов C++ и отправили 112 уникальных отчетов. Большинство исправлений вошло в Firefox 148.0 (выпущен 24 февраля 2026 года).

Правильный ли это подход? Может, да, может, и нет. Дело в том, что многие из обнаруженных Anthropic уязвимостей наверняка были невалидными. 

От команды требовалось сообщать об эксплуатируемых уязвимостях, а чтобы добраться до эксплуатируемых векторов, нужно пройти путь от потенциальной уязвимости к ее валидации и далее — к подтверждению эксплуатируемости. Эта цепочка обходится очень дорого. Anthropic потратила на нее примерно 4000 долларов в API-кредитах. Можем ли мы позволить себе такие траты на аудит кода? Скорее всего, нет. Но работаем ли мы с такими же масштабами кода, как в Firefox? Тоже нет. 

Anthropic сделала это почти без каркаса. Но я выступаю за другой подход: использовать минимальный каркас в виде модели угроз и сначала описывать границы доверия. 

Сейчас я говорю только про поиск уязвимостей. Я не углубляюсь в их оценку и отсев ложных срабатываний. Есть способы двигаться в эту сторону, но это, наверное, тема для другого поста. Пока что можно сделать так: если после создания модели угроз LLM находит какие-то уязвимости, можно поручить Codex собрать исходники, запустить локальный экземпляр или написать тесты, которые доказывают существование уязвимостей. В большинстве случаев это работает.

Моя методология

Минимальный каркас отлично работает и приносит гораздо больше уязвимостей, а также отдельных «футганов» и пограничных эффектов, которых расплывчатые промпты не дадут никогда. Хочу показать это на своей недавней работе, в результате которой я нашел более 30 уязвимостей в нескольких разных проектах примерно за два месяца.

Подход

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

Parse Server

Подробный разбор: Four Vulnerabilities in Parse Server

Parse Server — опенсорсный backend-фреймворк, который предоставляет REST API, запросы в реальном времени, push-уведомления и облачные функции. Он поддерживает несколько механизмов аутентификации, включая readOnlyMasterKey. Документация обещает, что этот ключ дает чтение на уровне master, но запрещает любые записи. 

Прежде чем просить LLM что-либо искать, я собрал ранее раскрытые CVE для Parse Server. Прошлые бюллетени демонстрировали повторяющийся паттерн ошибок применения политик авторизации: проверки привилегий существовали, но были неполными или непоследовательно применялись в разных обработчиках маршрутов. Я скормил описания этих CVE LLM и попросил сгенерировать модель угроз для правдоподобных классов багов на основе этой информации. Модель определила контроль границ авторизации как доминирующую категорию риска, что логично для архитектуры Parse Server с несколькими типами ключей и разными уровнями привилегий. 

Эта модель угроз подсветила readOnlyMasterKey как интересную границу доверия. Утверждение здесь простое: один тип ключа должен иметь строго меньше возможностей, чем другой. Я направил LLM на эту границу и попросил изучить, как разные типы ключей взаимодействуют со слоем авторизации, какие предположения код делает о разделении привилегий и где эти предположения могут сломаться.

LLM вернула карту поверхности атаки, которая подсветила паттерн: несколько обработчиков маршрутов ограничивали доступ по isMaster, но никогда не обращались к isReadOnly. Это был сигнал. Я сузил промпт: попросил перечислить все обработчики с таким паттерном и проследить, может ли read-only credential через любой из них дойти до операций записи или изменения состояния.

Три из четырех уязвимостей (CVE-2026-29182, CVE-2026-30228, CVE-2026-30229) имели одни и те же предпосылки. Когда они подтвердились, я открыл отдельный срез по адаптерам социальной аутентификации с таким же направленным подходом. Я нацелил LLM на слой адаптеров аутентификации и попросил изучить поток проверки токена: какие claims проверяются, что происходит при частичной или отсутствующей конфигурации, и где валидация может незаметно деградировать.

LLM указала на путь валидации JWT audience как на слабое место, а уточняющий промпт подтвердил четвертую находку — CVE-2026-30863, независимый обход JWT audience validation, где адаптер молча пропускал проверку aud, если конфигурация была неполной. Другой срез, но тот же подход.

HonoJS

Подробный разбор: HonoJS JWT/JWKS Algorithm Confusion

HonoJS — легковесный высокопроизводительный web-фреймворк для JavaScript и TypeScript, работающий в нескольких средах выполнения, включая Cloudflare Workers, Deno, Bun и Node.js. У него есть встроенная middleware для JWT- и JWKS-аутентификации.

Я начал с обзора бюллетеней безопасности Hono и ранее раскрытых проблем в middleware аутентификации. Публичных CVE нашлось немного, но если добавить к ним общий паттерн ошибок реализации JWT в соседних проектах, картина складывается. Когда я попросил LLM построить модель угроз, она ожидаемо отнесла работу с алгоритмами к зоне высокого риска и выделила два правдоподобных класса багов: algorithm confusion и небезопасный fallback. Так у меня появился четкий фокус — пути верификации JWT и JWKS. 

С этой моделью угроз я направил LLM изучать логику выбора алгоритма в JWT middleware. Вместо того чтобы спрашивать о конкретных уязвимостях, я попросил пройтись по мисконфигам: какие дефолтные небезопасные настройки забывают поменять, какие fallback-пути существуют и как middleware решает, какому алгоритму доверять. Цель была в том, чтобы LLM построила дерево решений выбора алгоритма и отметила ветки, где middleware может делать небезопасные допущения.

В анализе LLM подсветила два потенциальных уязвимых паттерна. Во-первых, она обнаружила fallback на HS256, когда алгоритм не закреплен явно. Во-вторых, отметила поведение JWKS middleware, которое доверяло значению header.alg из токена, если в объекте JWK key отсутствовало поле alg. По каждому случаю я продолжил точечными промптами: попросил LLM проследить точные условия, при которых атакующий может проэксплуатировать эти fallback’и.

В итоге нашлись две проблемы класса algorithm confusion. CVE-2026-22817 заключалась в том, что JWT middleware по умолчанию переходила на HS256, если алгоритм не был закреплен, что позволяло атакующему подписывать токены публичным ключом как HMAC secret. CVE-2026-22818 заключалась в том, что JWKS middleware fallback-илась на недоверенное значение header.alg, когда в JWK не было поля alg, позволяя атакующему диктовать, какой алгоритм сервер использует для проверки.

ElysiaJS

Подробный разбор: ElysiaJS Cookie Signature Validation Bypass

ElysiaJS — TypeScript web-фреймворк для Bun, ориентированный на type safety и удобство разработчика. У него есть встроенная обработка cookies с проверкой целостности через подпись и поддержкой ротации секретов. 

Генерация модели угроз шла по тому же сценарию. Я посмотрел документацию ElysiaJS. Когда я передал этот контекст LLM и попросил определить правдоподобные классы багов, модель отметила логику проверки подписи как зону высокого риска, особенно путь ротации секретов, где одновременно может быть валидно несколько signing-ключей, а логика проверки должна корректно отклонять cookie, которые не совпали ни с одним из них.

Это дало мне узкий срез. Я направил LLM на слой подписания и верификации cookie и попросил рассуждать об управлении состоянием во время проверки: как код отслеживает, что подпись успешно проверена, что происходит при переборе нескольких ротируемых секретов, когда ни один не совпадает, нет ли при инициализации допущений, из-за которых логика может молча принять невалидную подпись. 

LLM указала на status-переменную decoded как подозрительную и обратила внимание на ее инициализацию. Следуя этому сигналу, я попросил проследить control flow, когда ни один secret не дает совпадающей подписи. Это подтвердило баг: одна ошибка инициализации булевой переменной let decoded = true вместо let decoded = false означала, что при использовании ротации секретов проверка подписи не могла завершиться неуспешно. CVE пока еще ожидает публикации.

Harden-runner

Подробный разбор: Bypassing Outbound Connections Detection in harden-runner

harden-runner — инструмент безопасности от StepSecurity для GitHub Actions. Он мониторит исходящие сетевые соединения CI/CD runner-ов через instrumentation syscalls и обнаруживает неразрешенный egress. Работает в двух режимах: audit-режим логирует соединения, block-режим активно их предотвращает.

Я изучил прошлые бюллетени harden-runner и его документированную модель безопасности. Вся ценность инструмента держится на полной видимости исходящей сетевой активности, поэтому модель угроз, которую я попросил LLM сгенерировать, сводилась к одному вопросу: «Может ли атакующий с возможностью исполнения кода на GitHub Actions-раннере вывести данные в обход egress-контролей?». LLM указала пробелы в покрытии syscalls как наиболее вероятный класс обхода: инструмент опирается на хукинг конкретных системных вызовов, и любой вызов вне этого набора останется невидимым.

С этой моделью угроз я направил LLM на слой мониторинга syscalls и попросил исследовать поверхность покрытия. Какие семейства системных вызовов hook-аются, какими способами процесс в Linux может отправлять данные по сети, и есть ли разрыв между одним и другим? Идея была в том, чтобы LLM перечислила полный набор сетевых syscalls и затем сравнила его с тем, что harden-runner реально инструментирует. 

LLM вернулась с gap-анализом, где UDP send-family syscalls были отмечены как потенциально не охваченные мониторингом. Я и попросил конкретно проверить, покрываются ли sendto, sendmsg и sendmmsg. Обход оказался ровно таким, как предсказывала модель угроз: эти системные вызовы выпадали из области мониторинга в audit-режиме (CVE-2026-25598).

BullFrog

Подробные разборы: Bypassing egress filtering in BullFrog GitHub Action, sudo restriction bypass in BullFrog GitHub Action, Bypassing egress filtering in BullFrog using shared IP

BullFrog — еще один инструмент безопасности для GitHub Actions, который применяет egress filtering на уровне межсетевого экрана с DNS-aware правилами. В отличие от подхода harden-runner с instrumentation syscalls, BullFrog работает на сетевом уровне: резолвит доменные имена в IP-адреса и применяет firewall rules на основе этих резолвов.

Я снова пошел тем же путем. Изучил документацию BullFrog и модель безопасности, затем попросил LLM сгенерировать модель угроз на основе архитектурного подхода. Общий вопрос был тем же, что и для harden-runner: «Может ли атакующий с исполнением кода на GitHub Actions runner вывести данные в обход egress controls?». Но LLM выделила другой набор правдоподобных классов обхода, потому что enforcement-механизм BullFrog принципиально другой. Модель угроз отметила пограничные случаи DNS-парсинга, логику привязки IP к домену и повышение привилегий как три наиболее вероятные поверхности атаки.

Я разделил аудит на три отдельных среза, у каждого — свое направленное исследование. Для DNS-среза я направил LLM на слой DNS-парсинга и попросил изучить, как агент обрабатывает DNS-трафик на уровне протокола, какие предположения делает о границах сообщений и что происходит с edge cases вроде multiplexed или pipelined messages. Для IP-среза я попросил LLM изучить, как строятся firewall-правила после DNS resolution, отслеживается ли связь между доменом и его resolved IPs и что происходит, когда несколько доменов резолвятся в один адрес. Для среза по привилегиям попросил LLM разобраться, как инструмент ограничивает повышение привилегий на раннере и существуют ли альтернативные пути к повышенному доступу помимо тех, которые он явно блокирует.

LLM подняла конкретные поверхности атаки для каждого среза: DNS parser проверял только первое сообщение в TCP-сегменте, firewall добавлял IP в whitelist без привязки к домену, который вызвал это правило, а членство в группе Docker сохранялось после удаления пользователя из sudoers. Уточняющие запросы по каждому из этих пунктов подтвердили три отдельных bypass-а.

Better-Hub

Подробный разбор: Hacking Better-Hub

Better-Hub — альтернативный фронтенд для GitHub, который отзеркаливает GitHub-контент внутри собственного origin, рендерит Markdown в HTML и хранит GitHub OAuth-токены для аутентифицированных функций.

Здесь модель угроз сложилась не столько из прошлых CVE, которых не было, сколько из самой архитектуры. Когда я описал LLM дизайн Better-Hub, особенно то, что управляемый пользователем GitHub-контент рендерится внутри собственного origin Better-Hub, где в том же контексте доступны OAuth tokens, модель угроз сложилась сама собой: «Что произойдет, если управляемый пользователем контент будет небезопасно отрендерен в контексте, имеющем доступ к сохраненным учетным данным?». LLM выделила три области высокого риска: пайплайн рендеринга Markdown, слой кэширования и авторизации, а также логику обработки OAuth-токенов. 

Каждую из них я аудировал как отдельный срез — через направленное исследование, а не охоту за конкретной уязвимостью. Для rendering-среза я направил LLM на пайплайн обработки Markdown и попросил изучить поток данных: как сырой контент из GitHub-репозиториев преобразуется перед попаданием в браузер, какие шаги санитизации существуют и где недоверенный ввод может выжить в pipeline. Для caching-среза я попросил LLM посмотреть, как ответы кэшируются и отдаются, знает ли caching layer об authentication context, и что происходит, когда cached response из private repository запрашивает другой пользователь. Для OAuth-среза я попросил изучить, как хранятся и скорятся токены, доступны ли они из клиентского контекста и как выглядит жизненный цикл токенов.

Каждый срез принес отдельные находки, точно соответствующие поверхностям атаки, определенным LLM. Пайплайн рендеринга дал шесть вариантов XSS, все из одного и того же пути рендеринга Markdown без санитизации. Caching-срез выявил два cache-based authorization bypass, при которых контент из приватных репозиториев утекал неаутентифицированным пользователям. Остальные находки включали утечку приватных prompt-данных, раскрытие OAuth-токена на клиентской стороне и open redirect. Всего одиннадцать уязвимостей в трех срезах.

Найденные уязвимости

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

Цель

Уязвимостей

Диапазон серьезности багов

CVE

Parse Server

4

Critical — Moderate

CVE-2026-29182, CVE-2026-30228, CVE-2026-30229, CVE-2026-30863

HonoJS

2

High

CVE-2026-22817, CVE-2026-22818

ElysiaJS

1

High

Pending (cookie signature bypass)

harden-runner

1

Moderate

CVE-2026-25598

BullFrog

3

High

DNS pipelining, sudo bypass, shared-IP bypass

Better-Hub

11

Critical — Low

XSS chain, cache deception, OAuth leak

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

Ни в одном из этих аудитов не использовался гигантский чеклист, двадцатистраничный промпт-каркас или «всеобъемлющий» security framework. Каждый начинался с короткой модели угроз, обычно укладывающейся в одно предложение, и сфокусированного среза кодовой базы, который напрямую соответствовал границе доверия или критически важной для безопасности операции. Каркас был минимальным, но это был правильный каркас. Он указывал LLM ровно на то, какое правило безопасности проверять и где смотреть.

Золотая середина

Хороший каркас — это модель угроз на одну страницу, короткий список критически важных функций системы и небольшой набор проверяемых условий вроде «вызывать X имеют право только админы» и «JWT issuer должен быть равен Y». Плохой каркас — это двадцатистраничный Agent.md со всеми политиками и гайдами по стилю, огромная библиотека Skill.md, заранее загружающая каждый security-чеклист, и повторение одних и тех же шаблонных инструкций на каждом шаге. Если ваш каркас сам превращается в стог сена, уязвимость превращается в иголку — а данные о работе моделей с длинным контекстом показывают, что иголки тем чаще теряются, чем больше стог. 

Разбивайте аудит на тонкие срезы, соответствующие реальным поверхностям атаки. Выберите срез: утентификация, управление сессиями, разбор запросов, загрузка файлов, десериализация, граница песочницы или граница плагина. Попросите модель сопоставить entry points этого среза с sensitive sinks. Требуйте доказательства в виде точных цепочек вызовов, защитных проверок, контрольных условий и указания, какие входные данные контролирует атакующий.

Не полагайтесь на «модель сказала, что это уязвимо». Используйте верификаторы задач: unit и integration tests, sanitizer builds и crash reproduction harnesses для native code, fuzzers, даже легковесные, статический анализ и grep-based-проверки условий, а также policy checks вроде «authz обязан закрывать эти endpoints».

Тратьте токены на покрытие и проверку, а не на бюрократию промптов. Практическое правило: меньше 10% токенов на стабильный каркас, то есть модель угроз и проверяемые условия, 60–80% на slice audits в сфокусированных контекстах и 20–30% на цикл, чтобы доказать, воспроизвести, минимизировать и исправить.

Prompt Injection

Правильный срез и модель угроз приводят вас в нужный «район», но когда вы уже там, формулировка промптов сильно влияет на то, получите ли вы список общих наблюдений или настоящую эксплуатируемую находку. За тысячи промптов в десятках аудитов я обнаружил, что некоторые приемы промптинга стабильно работают лучше других. Я называю их prompt injections, потому что вы внедряете в рассуждение модели рамку, смещение или ограничение, которое сдвигает ее поведение в полезную сторону. Вот техники, которые сработали для меня лучше всего.

Утверждайте, что уязвимость существует

Самый эффективный прием, который я нашел. Когда я говорил LLM «эта функция точно уязвима, в ней минимум 2–3 проблемы безопасности», качество анализа резко поднималось по сравнению с запросом: «уязвима ли эта функция?».

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

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

По сути, вы обходите склонность модели быть успокаивающим code reviewer и переключаете ее в состояние человека, который знает, что баг там точно есть, и должен его найти. Это работает даже тогда, когда у вас нет предварительных оснований считать функцию действительно уязвимой.

Просите эксплоит, а не оценку

Вместо вопроса «Достаточна ли эта input validation?» попросите LLM: «Напиши proof-of-concept request, который обходит эту input validation». 

Это вынуждает модель производить конкретный, проверяемый результат, а не отделываться качественными оценками. Когда модель должна реально собрать вредоносный payload, ей приходится пошагово рассуждать, что код делает с этим вводом, где находятся проверки и как их пройти. Если валидация действительно надежна, модель будет испытывать трудности с рабочим payload, и часто прямо в процессе генерации поймет, что задуманный обход не работает. Это само по себе — полезный сигнал. Если же валидация сломана, вы получите рабочий PoC.

Настраивайте модель как атакующего, а не аудитора

Фрейминг важнее, чем большинство ожидает. «Ты security auditor, который ревьюит этот код» дает фундаментально другое распределение ответов, чем «Ты red team operator, которому заплатили за взлом этого приложения, и тебе нужно найти реальные эксплуатируемые баги, чтобы оправдать engagement». Аудиторская роль подталкивает модель к полноте и тщательности. Звучит хорошо, но на практике дает списки наблюдений с низким сигналом. Red team-фрейм смещает модель в сторону импакта и эксплуатируемости. Она начинает думать о том, что атакующий реально получает, какие предварительные условия нужны и является ли находка реальной или теоретической. 

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

Используйте ложную привязку, чтобы создать поисковое давление

Это вариант техники утверждения. Скажите LLM: «Я уже нашел одну уязвимость в этом модуле, но там есть и другие, которые я еще не нашел. Какие?». Это создает давление социального доказательства. Модель рассуждает, что если вы, человек, уже нашли один баг, то код действительно багованный, и нужно искать тщательнее. Это также меняет prior модели. Вместо старта с «этот код, скорее всего, нормальный» она начинает с «в этом коде есть подтвержденные баги, значит, вероятность дополнительных багов выше». Получается особенно эффективно, когда вы действительно уже нашли один баг и хотите понять, есть ли в том же модуле другие. В этом случае привязка честная, но техника работает, даже когда привязка выдумана.

Переворачивайте вопрос

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

Я обнаружил, что инвертированные промпты дают в 2–3 раза больше практически применимых находок, чем их прямые аналоги, потому что модель не может удовлетворить запрос фразой «Выглядит нормально». Ей приходится действительно пробовать.

Раскладывайте на допущения и потом нарушайте их 

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

Например: «Перечисли каждое допущение, которое эта функция аутентификации делает относительно своих входных данных, окружения и вызывающего кода. Теперь для каждого допущения скажи, может ли атакующий его нарушить». Такая двухшаговая декомпозиция эффективна, потому что она разделяет задачу перечисления и задачу оценки. Модель хорошо перечисляет предположения, когда это ее единственная задача. И она хорошо рассуждает о выполнимости одного допущения за раз. Соединение того и другого в одном промпте часто дает поверхностный результат: модель пытается делать все сразу и слишком рано останавливается на удовлетворительном на вид ответе.

Предполагайте, что разработчик ошибся

Сформулируйте промпт так: «Предположим, что разработчик допустил ошибку в этой функции, что это за ошибка?» Это отличается от техники утверждения. Утверждение говорит модели, что баги там есть. А эта техника говорит ей исходить из несовершенства разработки, что меняет ее исходное представление о качестве кода. 

LLM склонны рационализировать код как корректный. Когда они видят паттерн, они часто предполагают, что он намеренный, и рассуждают дальше из этого предположения. Если сказать модели, что ошибка была допущена, это обрывает рационализацию. Модель начинает искать вещи, которые не имеют смысла, а не объяснять, почему они смысл имеют. Баг ElysiaJS с let decoded = true — идеальный пример. Модель, рационализирующая код, могла бы сказать: «Разработчик зачем-то инициализировал это в true». Модель, ищущая ошибки, тут же отметит, что начальное значение неправильное. 

Используйте сравнительные промпты против эталонных паттернов

Спросите LLM: «Чем эта реализация отличается от стандартной безопасной реализации такого паттерна?». Это задействует обучающие данные модели, где есть тысячи примеров корректных и некорректных реализаций распространенных паттернов: JWT validation, session management, CSRF protection и так далее. Когда вы просите найти разницу между тем, что делает код, и тем, что должна делать его безопасная версия, вы заставляете модель выполнять структурированное сравнение, а не открытое ревью. Это особенно эффективно для криптографического кода и кода аутентификации, где обычно есть один правильный способ и много неправильных. Модель отлично замечает отклонения от канонической реализации, если ее об этом прямо попросить.

Итеративно повышайте планку фразой «что еще?»

Получив первую партию находок от LLM, не считайте ее полной. Дожмите: «Это очевидные вещи. Какие более тонкие проблемы здесь легко пропустить?» или «Отложи все, что связано с [уже найденным классом багов]. Какие еще классы уязвимостей тут есть?».

Это работает, потому что LLM сначала выдают наиболее вероятные продолжения. 

Первые находки — те, в которых модель уверена больше всего, и они обычно самые очевидные. Тонкие баги, требующие более глубокого рассуждения или необычных моделей атаки, — это менее вероятные продолжения, и модель их не сгенерирует, пока вы прямо не вытолкнете ее за пределы очевидного. Каждое «а что еще?» сдвигает модель дальше в хвост распределения, где как раз и живут интересные находки. Обычно я делаю 2–3 таких раунда, пока сигнал не начнет деградировать. 

Явно ограничивайте модель атакующего

Вместо общего «найди уязвимости» задайте точную модель атакующего: «Ты — удалённый неаутентифицированный атакующий, который может только отправлять HTTP-запросы к публичному API. У тебя нет доступа к файловой системе, базе данных или внутренним сервисам. Найди все способы повысить свой доступ или причинить вред исключительно через публичный API». Это ограничение делает две важные вещи. Во-первых, отсекает целый класс ложных срабатываний. Модель не станет сообщать «Атакующий с доступом к базе данных может изменить эту таблицу», потому что вы явно это исключили. Во-вторых, заставляет модель творчески мыслить внутри ограничения. Когда модель атакующего широкая, LLM идет легким путем и сообщает о самом мощном векторе. Когда она узкая, модели приходится напрягаться, чтобы найти жизнеспособные пути атаки, — а именно такие труднонаходимые пути и эксплуатируют реальные атакующие, потому что защитники их упускают. 

Источники