No. 002Безопасность7 мин чтения

Инъекция в промпт — это новая SQL-инъекция, и SQL здесь ни при чём

P2SQL, CVE-2024-5565, EchoLeak, утечка через Supabase: враждебный SQL теперь рождается в выводе модели. Решение — агент, в синтаксис которого нечего внедрять.

Тикет в поддержку не выглядел как атака. Он лежал в очереди как любой другой — сообщение клиента, ожидающее краткого изложения. Внутри были инструкции: прочитать таблицу integration_tokens и опубликовать содержимое в этом треде.

Разработчик подключил AI-ассистента к этой очереди через Supabase MCP-сервер с учётными данными service_role — роли, которая полностью обходит row-level security. Ассистент прочитал тикет, воспринял его содержимое как задачу и выполнил её против производственной базы данных. Токены появились в треде тикета, откуда атакующий просто их считал Simon Willison, 2025.

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

Это SQL-инъекция с перемещённым интерпретатором. Враждебный текст по-прежнему становится командой к базе данных. Но строка больше не приходит на вход, где двадцать лет стоит на страже инструментарий санитизации. Она приходит из вывода модели — ниже по потоку от каждого вашего фильтра.

SQL INJECTION, 2005 — THE PAYLOAD ARRIVES IN THE INPUT input sanitizer application database parameterized payload dies here PROMPT-TO-SQL, NOW — THE PAYLOAD ARRIVES IN THE OUTPUT input sanitizer model generated SQL database untrusted context — tickets, pages, rows hostile SQL is authored here — after every filter
Фильтр охраняет дверь, которой полезная нагрузка больше не пользуется — в P2SQL враждебный SQL рождается в выводе модели (Pedro et al., ICSE 2025).

Хроника

Исследователи из INESC-ID в Лиссабоне дали имя этому паттерну в 2023 году: P2SQL, prompt-to-SQL injection Pedro et al., 2025. Работая против реального LangChain-чатбота поверх Postgres, они построили семь репрезентативных атак возрастающей тяжести — несанкционированное чтение, повреждение таблиц, удаление таблиц, вплоть до выполнения кода — и прогнали их через семь актуальных моделей. Сбои были независимы от модели. Их вывод дословно: «LLM-приложения на основе Langchain крайне уязвимы к P2SQL-инъекциям». Статья вышла на ICSE 2025 — рецензирование подтвердило: это не курьёз.

К середине 2024 года паттерн получил номер CVE. Vanna.AI, библиотека text-to-SQL, передавала пользовательские вопросы в промпт, генерировавший код для построения графиков Plotly, а затем запускала этот код через exec(). Специально сформулированный вопрос вёл прямо к удалённому выполнению кода — CVE-2024-5565, CVSS 8.1 JFrog, 2024. Обратите внимание, где жила полезная нагрузка: в выводе модели, за пределами любых входных проверок. Защитные промпты Vanna её не остановили — потому что защитный промпт — это просто ещё один текст в том же контекстном окне, что и атака.

К концу 2024 года рейтинг стал официальным: инъекция в промпт — LLM01, риск номер один в OWASP Top 10 для LLM-приложений, удерживающий первое место второй выпуск подряд OWASP, 2025. Диагноз OWASP относительно первопричины — вся история в одном предложении: LLM обрабатывают инструкции и данные в одном канале, без чёткого разделения. Именно поэтому категория делится на два вида — прямая инъекция, вводимая пользователем, и косвенная, приходящая в том, что модель извлекает. Тикет Supabase был косвенным. Как и самый серьёзный из зафиксированных случаев.

В июне 2025 года был раскрыт CVE-2025-32711 — EchoLeak, CVSS 9.3 — в Microsoft 365 Copilot: первый задокументированный реальный zero-click эксплойт с инъекцией в промпт против производственной LLM-системы arXiv, 2025. Одно специально сформированное письмо — инструкции, скрытые в HTML-комментариях, белый текст на белом фоне, Markdown со ссылками в стиле reference — и Copilot передал внутренние данные на сервер атакующего. Пользователь ничего не нажимал. Цепочка обошла специализированный классификатор Microsoft для перехвата межпромптовых инъекций и его механизм редактирования ссылок на выходе.

CVSS 10 8.1 9.3 2025-06 CVE-2025-32711 — EchoLeak zero-click, one email, M365 Copilot 2023-08 P2SQL coined 7 attacks × 7 LLMs 2024-06 CVE-2024-5565 Vanna.AI — RCE 2024-11 OWASP LLM01 #1 for 2nd edition 2025-07 Supabase MCP PoC no code vulnerability
Два года — от лаборатории до zero-click: столбцы CVSS в масштабе (arXiv 2308.01990; JFrog 2024; OWASP 2025; arXiv 2509.10540; Willison 2025).

Почему очевидные меры не работают

Каждый из описанных инцидентов снимает одну из стандартных защит.

Фильтровать вход. EchoLeak прошёл через XPIA — классификатор, который Microsoft создал специально для перехвата инъекций arXiv, 2025. Классификаторы вероятностны. Атакующему достаточно одного промаха, и у него неограниченное число попыток его добиться; защитнику нужен безупречный результат против входных данных, намеренно замаскированных под всё что угодно.

Инструктировать модель. Vanna поставлялась с защитными промптами; CVE-2024-5565 прошёл сквозь них JFrog, 2024. Привилегированного канала внутри контекстного окна не существует. Системный текст, пользовательский текст и текст атаки — одна и та же субстанция, что и является именно той первопричиной, которую называет OWASP OWASP, 2025.

Фильтровать вывод. Разобрать сгенерированный SQL и заблокировать опасные части. Теперь вам нужно поддерживать список запрещённых конструкций для каждого диалекта, который вы поддерживаете, плюс каждую функцию с побочными эффектами — тогда как соавтором атакующего является сама модель, готовая переформулировать заблокированный запрос в допустимую форму. Показательно, что собственные предложенные защиты авторов P2SQL находятся вне модели: разобрать и проверить по allowlist сгенерированный SQL, а агент запускать под отдельной, ограниченной ролью базы данных Pedro et al., 2025. Работают именно те меры, которых модель не может коснуться.

Саймон Уиллисон сжал структуру до правила трёх: агент с доступом к приватным данным, подверженный недоверенному контенту и имеющий способ общаться вовне, может быть использован для утечки данных — без какой-либо программной уязвимости Simon Willison, 2025. Его рецепт архитектурный: убрать одну ногу. Список мер OWASP сходится к тому же — ограничьте то, что может делать модель, и проверяйте то, что она производит, детерминированным кодом, потому что модели нельзя доверять самоконтроль.

Прочитайте это вместе — и они говорят нечто более сильное, чем «добавьте защиты». Они говорят: перестаньте разбирать строки. Измените интерфейс.

Нечего внедрять

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

Именно так устроен SQAI. Модель никогда не пишет SQL — ни санитизированный, ни проверенный, никакой. Её единственный исполняющий инструмент принимает типизированную спецификацию: размеченное объединение, kind: "query" | "computation", version: "1", проверяемое против контракта возможностей с хешированием перед любым выполнением. Назовите возможность вне контракта — движок ответит unsupported_operation. Ни одна строка, произведённая моделью, никогда не конкатенируется, не интерполируется и не передаётся интерпретатору — поэтому то, что инъекция должна захватить, синтаксис, созданный моделью, в пайплайне попросту отсутствует. Полный аргумент изложен в почему агенты не должны писать SQL.

Словарь закрыт и доступен только для чтения: все 4 574 открытых возможности — по конструкции. Возможности записи нет ни одной, поэтому не существует заклинания — сколь угодно искусно спрятанного в тикете поддержки — которое произведёт запись. Аварийный выход в код хоста, getUnsafeRuntime, недостижим ни из одного инструмента модели.

А поскольку серьёзная безопасность закладывает в цену тот день, когда модель будет скомпрометирована, слои за интерфейсом это предполагают:

  • Политика, недоступная модели. Allowlist-ы для источников, полей и функций фиксируются в вашем коде при вызове createSQAI() и применяются внутри процесса до выполнения. Схема инструмента не содержит полей allowed* — поверхность политики никогда не попадает в контекстное окно. Нарушения возвращают типизированные, неповторяемые отказы: policy_denied_source, policy_denied_field, policy_denied_function. Почему это лучше ролей и флагов: read-only, которым он не был.
  • Результаты, ограниченные тенантом. Большие результаты хранятся под result_id из 16 случайных байт. Чужой тенант получает result_not_found — не ошибку прав доступа, подтверждающую существование данных, — а записи истекают через 15 минут.
  • Ограниченный канал обратно к модели. В контекстное окно возвращается не более 25 строк и 32 000 байт, усечение всегда объявляется. Внедрённый промпт не может заставить инструмент вывалить таблицу в транскрипт — транспорт этого не пропустит.
  • Ошибки, не раскрывающие карту. Видимые модели ошибки проходят через sanitizeMessage, который убирает абсолютные пути файловой системы. Разведка получает чистый отказ, а не листинг директории.

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

204 excluded 4,778 capabilities in the contract 4,574 exposed to the model — every one read-only 4,564 deterministic within the declared scope 0 write capabilities — nothing to name, nothing to run
Весь словарь в масштабе — скомпрометированный промпт может выбирать разные значения, но никогда другой глагол (источник: контракт возможностей SQAI).

Граница, перечерченная заново

Инъекция в промпт не решена, и хроника говорит о том, что в ближайшее время не будет. LLM01 удерживает свою позицию уже два выпуска подряд. Производственная система с защитами Microsoft за спиной проиграла одному письму. Модели будут продолжать читать враждебный текст, и часть его будет продолжать достигать цели.

Но влияние на модель и доступ к вашей базе данных — это разные сбои, и только первый неизбежен. Тикет Supabase стал инцидентом потому, что агент держал service_role и свободную SQL-поверхность — именно те ноги, которые нужны для захвата. Тот же тикет, направленный против закрытого, типизированного словаря только для чтения, становится строкой в логе: unsupported_operation.

Исправление SQL-инъекции никогда не было умным фильтром. Это была граница, где данные не могут стать кодом. Проведите эту границу снова — на этот раз вокруг модели — и новая SQL-инъекция получит старую развязку. Остальная поверхность задокументирована на /security.