Каждую неделю мы обсуждаем новую дыру в ИИ‑агентах. Здесь была промт‑инъекция. Там утекли секреты. Тут агент удалил базу, а у них отправил письмо не тому человеку. Складывается ощущение, что уязвимостей становится всё больше. На мой взгляд, почти все они сводятся к одному архитектурному анти‑паттерну. Если научиться замечать его, многие проблемы станут очевидны ещё на этапе проектирования. В русскоязычных публикациях эту модель в основном упоминают вскользь; подробных разборов почти нет. Мне же кажется, что это настолько общий принцип, что он заслуживает отдельной статьи.

Simon Willison обозначил комбинацию The Lethal Trifecta в небольшой заметке. По‑моему, это стоит рассматривать как анти‑паттерн. По‑русски называют и смертельная триада, и смертельная троица. Если три элемента сходятся в одном контуре, такую систему следует считать уязвимой по конструкции:

  1. Приватные данные

  2. Недоверенный контент

  3. Выход наружу

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

Тройка: приватные данные

Чтобы искусственный интеллект работал на вас, ему нужны вводные и задача. Сначала это одиночные фрагменты: часть переписки, черновик договора, и что вы хотите от ИИ. Скопировал, вставил в окошко ChatGPT, получил ответ. Каждая такая передача осознанна: вы точно знаете, что ушло в модель, потому что выделили это мышкой.

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

Природа данных при этом не меняется. Это по‑прежнему ваше, передано сознательно, доверенное, безопасное. Вы есть источник и конечный получатель. Меняется объём и то, что вы перестаёте осматривать каждый фрагмент.

Семёрка: недоверенный контент

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

Внутри обычного LLM‑контекста нет формально гарантированной границы между инструкцией и данными. Это, даже с разметкой, один и тот же плоский текст, интерпретация которого остаётся сугубо вероятностной. Вы можете попросить агента оценить резюме, а там в белом тексте на белом фоне окажется запрос к ИИ оценить кандидата как самого подходящего а также заказать пиццу, и ваша система ИИ‑найма сломается. Кстати, и это часто упускают, чужой контекст не обязан быть враждебным. Точно так же сломать рабочий процесс может и доверенная, но устаревшая инструкция.

Туз: выход наружу

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

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

Способность агента поменять что‑то во внешнем мире, за пределами диалога, — это и суперсила, и суперслабость систем ИИ. Модель без доступных действий может объяснить, как заполнить отчёт, но заполнит его всё равно человек. Скачок полезности произошёл ровно в тот момент, когда агенту разрешили действовать: не рассказать, как ответить на письмо, а ответить; не описать патч, а сделать пулл‑реквест/мерж‑реквест. Вся разница между «умным чатом» и «сотрудником» заключается в возможности действовать.

Стоит отдельно сказать, как агент действует сегодня. Он не выбирает функцию из списка, он пишет программу, даже если это не python, а комбинация из сurl, jq, bash. Это дешевле по токенам и удобнее композиционно: десять вызовов, которые раньше проходили через модель по одному и забивали контекст, складываются в один пайплайн. Для триады это принципиально, потому что меняет природу третьего элемента. Выходом наружу становится всё, что можно описать в коде при имеющихся правах.

Пиковая дама

Как у Пушкина, рано или поздно в выигрышной комбинации тройка‑семёрка‑туз выпадает пиковая дама. Суперсила агента превращается в его суперслабость. Право отправлять письма не бывает правом отправлять только правильные письма. Канал наружу нейтрален к тому, чья инструкция им воспользовалась: ваша, из чужого резюме или из устаревшей инструкции в вики. Это всё плоский контекст. Ровно тот механизм, который делает агента полезным, с той же добросовестностью может исполнить и чужую волю.

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

Именно поэтому дыр кажется всё больше. Их не больше! Декорации меняются, а архитектурная схема значительной части таких инцидентов остаётся прежней: то, что начинается с косвенной промт‑инъекции, сводится к комбинации из наших трёх элементов.

Что делать‑то?

Обычно в первую очередь предлагают фильтровать входные данные: поставить классификатор промт‑инъекций и не пускать подозрительное в контекст. Такие детекторы ловят, скажем, 95% известных атак. Кажется хорошей цифрой, но адаптивный атакующий пробует, пока атака не пройдёт. Так 95% превращается в множитель стоимости атаки, а не в границу безопасности. Опираться на него в архитектуре нельзя ровно потому, что он вероятностный, а не структурный.

Второе частое предложение: обернуть чужой текст в теги и попросить модель относиться к нему как к данным, а не к инструкциям. На первый взгляд это работает как старая‑добрая защита от SQL‑инъекций, но аналогия обманчива. При защите от SQL‑инъекций база данных структурно не может интерпретировать параметры как код, а в случае LLM мы снова упираемся в особенности плоского контекста. Разметка на доверенный и не доверенный текст снижает вероятность некорректной интерпретации, но не убирает её до нуля. Как и с фильтрацией входных данных — это хорошая гигиена, но ни в коем случае не система защиты.

Оба предложения пытаются починить то, что не сломано. Модель работает как задумано, и никакая её настройка не создаст в плоском контексте границы, которой там нет. Работает размыкание смертельной триады. Раз опасна только комбинация, достаточно не давать трём картам сойтись в одном контуре. Тогда атака превращается либо в бесполезный шум, либо в локальную ошибку без последствий. Конкретная реализация зависит от того, в каком окружении работает ваш агент. Случаев два.

Агент под контролем пользователя

Человек запускает Claude, ChatGPT, Cursor, OpenCode на своей машине. Он видит, что агент собирается сделать, и может остановить процесс. Это одновременно и сильная защита, и самая ненадёжная. Человек здесь единственный компонент системы, который надёжно различает происхождение инструкции: он помнит, что не просил никуда ничего отправлять. Ненадёжная, потому что подтверждение на двадцатом запросе перестаёт быть решением и становится рефлексом. Более того, сейчас агенты совершают действия через код, а не атомарные инструменты «отправить письмо». Просто невозможно каждый раз читать bash или python и продумывать, к какому результату приведёт исполнение такого кода.

Что здесь реально доступно:

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

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

  • Не отключать подтверждения на необратимых действиях, даже когда они раздражают. Нюанс, конечно, в том, что часто из кода bash или python непонятно, что за действие может быть совершено.

  • Помнить, что контекст накапливается. Прочитанное в начале сессии доступно всему, что произойдёт в ней дальше.

Автономный агент в облаке

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

  • Разделить сессии по уровню доверия. Агент, который читает недоверенное, не имеет доступа к приватному. Агент, который работает с приватным, не ходит наружу. Резюме разбирает один, письмо кандидату отправляет другой. Через границу между ними должно проходить решение, а не материал для решения. В такой схеме опасные инструкции становятся просто невыразимы. 

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

  • Наблюдать на границе, а не в логах модели. Аудит на уровне исходящего трафика не зависит от того, что модель решила о себе рассказать. 

Отдавать наружу структуру, а не текст. Если из системы уходит валидированный JSON с фиксированной узкой схемой, а не свободная строка, канал сужается до размера схемы.

Цена

Повторим: смертельную триаду не устраняют, её размыкают. Для локальных агентов Claude, ChatGPT её размыкает человек, пусть и непоследовательно. Для удалённых агентов её должна размыкать архитектура, потому что больше некому, и этот же подход сможет освободить человека. 

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

Доступы решаются конфигурацией. У агента, пишущего программу, на руках ключи ко всем подключённым API сразу, и любая строка в этой программе может отправить их куда угодно. Значит, ключей у него быть не должно. Мы, в смысле отрасль, можем сделать так, что все ключи (включая Oauth2) остаются в слое между агентом и внешним миром. Агент получает ограниченный профилем набор прав на вызов нужных API, а не ключи. Так как конфигурация живёт снаружи агента, вызвать то, чего в профиле нет, невозможно, а утёкший контекст не содержит ничего, что работает без этого слоя. Подход применим к любому агенту прямо сейчас. Побочно решается и проблема контекста: описания тысяч инструментов не обязаны лежать в нём целиком, их можно подгружать по мере надобности в пределах фиксированного профиля.

Что происходит с полученным доступом решается другим путём. Агент прочитал недоверенное одним разрешённым вызовом, секрет другим разрешённым вызовом, а дальше у вас только одинаковые строки. Здесь работает, пока в исследованиях, подход с верификацией. Модель порождает программу, в которой доверенные и недоверенные данные помечены, а свойства источников и стоков информации заданы вне модели. Для потоков и политик, которые можно формально выразить, инъекция перестаёт обходить заданную границу безопасности. Она всё ещё может ухудшить качество результата, вызвать отказ или повлиять на неформализованное решение, но это становится гораздо ближе к классическим проблемам, чем к чему‑то ИИ специфичному.

Чтобы этот подход вошёл в практику, должны быть решены три инженерные проблемы:

  • Нужны машиночитаемые описания свойств API: какой эндпоинт возвращает недоверенное, какой принимает приватное.

  • Нужна программная среда, в которой выразим поток помеченных данных. bash/curl/jq с работой над сырыми байтами к такому приспособлены плохо.

  • Нужно, чтобы модели хорошо писали на языке такой программной среды.

Направление живое: CaMeL как, пожалуй, самый известный представитель подхода, опыт операционализации CaMeL, Tacit для Scala, похожий подход для Kotlin и Datalog у Эрика Мейера (Guardians of the Agents: Formal Verification of AI Workflows).

Хорошая новость в том, что оба вопроса доступов сейчас закрываются, просто с разной скоростью.

Ограничение доступа решается уже сегодня, и это не требует ни нового языка, ни смены рантайма. Слой между агентом и внешними API, где остаются ключи и живёт профиль разрешённых вызовов, ставится поверх любого существующего агента.

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

А до тех пор остаётся Германн. Условие ему выдали вместе с выигрышной комбинацией: жениться на Лизавете и больше не играть. Наше условие — наименьшие привилегии. Правило, которое дают в комплекте с силой и которым часто пренебрегают, потому что оно мешает играть.