Engineering the Agentic Stack · Часть 4

Безопасность ИИ-агентов: разрешения, сэндбоксы и угрозы MCP

Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.

Обновление статьи

Первоначально опубликовано 20 апреля 2026 года. Проверено и обновлено 6 сентября 2026 года. Обновление охватывает новые средства управления сэндбоксами, защитные вмешательства провайдеров и опубликованные результаты исследований безопасности, а также их ограничения и ссылки на источники.

Безопасность агента начинается в момент, когда модель предлагает действие, и заканчивается до того, как машина его выполнит. Заранее решите, какая проверка имеет последнее слово, прежде чем действие получит доступ к учетным данным, файлам, сетям или внешней системе.

Харнесс — это код, который формирует каждый промпт, решает, какие предложенные tool calls запускать, и возвращает результаты модели. Большинство проверок должно находиться именно там: это последнее дешевое место, где можно остановить команду. После выполнения команды сэндбокс, переданные ей учетные данные и процесс восстановления должны ограничить ущерб. Некоторые инциденты из этой статьи вообще не доходят до модели.

Безопасность ИИ-агентов шире, чем безопасность LLM. Ранние продукты для гардрейлов проверяли вход и выход одного вызова модели. Они могли фильтровать токсичный текст, редактировать персональные данные, блокировать jailbreak и отклонять ответы не по теме. Пока модель могла возвращать только текст, этой границы было достаточно.

Циклы с tool calls добавили файловые системы, shell, серверы Model Context Protocol (MCP) и учетные данные. Модель угроз расширилась: теперь речь идет не только об опасном тексте, но и об опасных действиях. Семь групп инцидентов ниже охватывают косвенную промпт-инъекцию, а также ошибки в конфигурации, идентификации и распространении ПО. Проверка текста может помочь с некоторыми вредоносными входными данными, но не заменяет контроль на этих границах выполнения.

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

Краткий чеклист контролей см. в разделе Чеклист безопасности ИИ-агентов.

Стек безопасности ИИ-агента

Ни один гардрейл не защищает агента сам по себе. Для каждой части системы нужна собственная проверка.

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

СлойЧто контролируетПример сбоя, который обнаруживаетГде находится
Фильтры контентаНебезопасный входной и выходной текстТоксичный вывод, утечка PII, ответы, нарушающие политикуХарнесс
Лестница разрешенийИнструменты, пути, APIs и области доступа агентаСуммаризатор пытается писать в production-системыХарнесс
Хук политики перед инструментомНужно ли запускать именно это действие сейчасShell-команда, собранная из непроверенного retrievalХарнесс
СэндбоксК чему инструмент может обращаться на уровне ОС и сетиЭкcфильтрация файлов, компрометация зависимостей, command injectionРантайм
Проверка человекомНеобратимые или высокорисковые действияОтправка письма, перевод денег, деплой в productionХарнесс
MCP и ограничение токеновДля какого сервера и аудитории действительны учетные данныеПовторное использование токена не тем сервером инструментаРантайм
Аудит-трейсЧто произошло, кто одобрил действие и почемуРасследование инцидента после долгого автономного запускаРантайм

Фильтры контента проверяют, не сказала ли модель что-то небезопасное. Безопасность агента дополнительно проверяет, разрешено ли системе выполнить следующее действие.

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

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

Управляемые фильтры контента покрывают текстовый слой. Остальные контроли должны находиться в политиках приложения, системе идентификации и инфраструктуре.


Чем безопасность ИИ-агентов отличается от безопасности LLM

Bharani Subramaniam и Martin Fowler задали эту рамку в начале 2025 года в статье Emerging Patterns in Building GenAI Products. Их наблюдение было узким и прямым:

«В традиционных системах мы могли оценивать корректность преимущественно с помощью тестирования… В системах на базе LLM мы сталкиваемся с системой, которая больше не ведет себя детерминированно».

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

Гардрейлы LLM оборачивают вызов модели; харнесс оборачивает циклГардрейлы LLM оборачивают вызов модели; харнесс оборачивает цикл

В июне 2025 года Simon Willison сформулировал специфический для агентов риск через смертоносную триаду:

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

Многие полезные агенты объединяют эти возможности: доступ к входящим письмам, веб-retrieval и инструмент обмена сообщениями; или доступ к репозиторию, чтение issue и запись pull request. Гардрейл контента спрашивает, сгенерировала ли модель небезопасный текст. Триада спрашивает, может ли непроверенный ввод направить систему к раскрытию данных через разрешенное действие.

Смертоносная триадаСмертоносная триада

Структурная версия того же аргумента изложена в препринте Joel Fokou Parallax (arXiv 2604.12986, отправлен 14 апреля 2026 года, не прошел peer review). Основной тезис:

«Система, которая рассуждает о действиях, должна быть структурно неспособна выполнять их, а система, которая выполняет действия, должна быть структурно неспособна рассуждать о них; между ними должен находиться независимый неизменяемый валидатор».

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

  • хуки PreToolUse в Claude Code;
  • экзекьютор Codex CLI в сэндбоксе ОС (в Linux — bubblewrap плюс фильтрация системных вызовов через seccomp);
  • Managed Agents от Anthropic, где учетные данные хранятся в vault, который агент никогда не видит;
  • токены MCP с привязкой к аудитории согласно RFC 8707.

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

Есть и дополнительная дисциплина, которую Alessandro Pignati наиболее четко сформулировал в январе 2026 года: Principle of Least Agency. Least Privilege спрашивает: «К чему может получить доступ эта идентичность?» Least Agency спрашивает: «Что этому агенту разрешено решать?» Привилегии ограничивают учетные данные; агентность ограничивает охват плана, даже если учетные данные валидны. Excessive Agency — отдельный пункт Top 10 для LLM-приложений, опубликованный OWASP, Open Worldwide Application Security Project. Рассматриваемый далее отдельный список угроз для агентных систем разделяет тот же сбой между неправомерным использованием инструментов и злоупотреблением привилегиями. Least Agency — это проектная дисциплина, предотвращающая оба сценария. Агенту, который может суммаризировать входящие письма, вероятно, не нужны права на коммиты в вашем монорепозитории. Мы по-прежнему находим конфигурации, где они есть.


Что покрывают гардрейлы LLM

Гардрейлы LLM выполняют важную работу вокруг вызова модели. Они проверяют входные данные, retrieved-текст и вывод, а затем блокируют, редактируют, исправляют или помечают контент, нарушающий настроенное правило. Приведенные ниже продукты различаются по способу деплоя и покрытию. Проверка контента отделена от проверки авторизации на границе инструмента или MCP-сервера; некоторые продукты также предлагают функции политик рантайма, которым нужны отдельные настройка и эвалуация.

NVIDIA NeMo Guardrails

Самый opinionated вариант: фреймворк оркестрации с пятью типами rail (input, dialog, retrieval, execution, output) и собственным DSL — Colang, похожим на Python языком для диалоговых потоков, интентов пользователей и сообщений бота. Базовые сценарии можно запускать из Python + YAML, но более сложная логика диалога пишется на Colang — отсюда и «opinionated». Документация: docs.nvidia.com/nemo/guardrails.

Это только иллюстрация формы API; для работы требуются пакет и настроенный каталог ./config.

from nemoguardrails import LLMRails, RailsConfig

config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(
    messages=[{"role": "user", "content": "Hello"}]
)

В репозитории NeMo прямо описана модель угроз: «распространенные уязвимости LLM, такие как jailbreak и промпт-инъекции». Там же прямо указаны границы: «Встроенные гардрейлы могут подходить или не подходить для конкретного production-сценария… разработчики должны работать со своей внутренней командой приложения, чтобы убедиться, что гардрейлы соответствуют требованиям». Показанный здесь путь проверки контента наблюдает за тем, что говорит модель. В актуальной документации NeMo также описаны execution rails, custom actions и проверка tool calls; это настраиваемые контроли рантайма, а не доказательство того, что развернутый инструмент или MCP-сервер аутентифицировал и авторизовал вызов. Эта граница по-прежнему принадлежит приложению.

Meta Llama Guard 4

Чистый классификатор контента на 12B, полученный из Llama-4-Scout и выровненный по таксономии опасностей MLCommons (13 категорий вреда плюс злоупотребление code interpreter согласно карточке модели). Meta необычно откровенно описывает ограничения:

«Некоторые категории опасностей могут требовать фактических и актуальных знаний для полноценной оценки… Наконец, как LLM, Llama Guard 4 может быть подвержена adversarial-атакам или атакам промпт-инъекции, которые обходят или изменяют ее предполагаемое использование: см. Llama Prompt Guard 2 для обнаружения атак на промпты».

Meta поставляет отдельный продукт для защиты своего классификатора контента от промпт-инъекций. Если это звучит как структурное признание, так оно и есть.

Guardrails AI

Реестр валидаторов. Вы комбинируете валидаторы Hub (PII через Presidio, JailbreakDetect, CompetitorCheck, проверки provenance) с действиями on_fail exception | fix | fix_reask | filter | refrain | reask | noop или собственным callback (guardrailsai.com). Обратите внимание: exception, а не raise. В текущем исходном коде нераспознанная строка on_fail передается в обработку custom callback и приводит к ошибке при настройке валидатора, а не к предупреждению с последующим fallback. Зафиксируйте версию, которую деплоите, и протестируйте этот путь ошибки. Единой модели угроз нет; покрытие равно объединению установленных валидаторов. Вы получаете защиту для всего, для чего есть валидатор, и никакой защиты для остального.

Lakera Guard

Устоявшийся SaaS API, обученный на десятках миллионов образцов атак, собранных через Gandalf. Он обещает проверять вход и выход на наличие «атак на промпты… и утечки данных». Отдельный продукт Lakera AI Agent Security также описывает применение политик и рантайм-контроль того, к чему агенты могут обращаться, что вызывать и что делать. Это другая поверхность продукта, не та же самая проверка контента, о которой идет речь здесь. Перед деплоем проверьте актуальный контракт продукта и тарифов.

AWS Bedrock Guardrails

Корпоративный вариант по умолчанию, если вы уже используете Bedrock. ApplyGuardrail работает с любой моделью — не только Bedrock:

# No-run: illustrative AWS request; requires boto3, AWS credentials, and a real guardrail identifier.
import boto3

brt = boto3.client("bedrock-runtime")
resp = brt.apply_guardrail(
    guardrailIdentifier="gr-xxxxxxxxxxxx",
    guardrailVersion="2",
    source="INPUT",
    content=[{"text": {"text": "user question",
                        "qualifiers": ["guard_content"]}}],
)

Опубликованная ApplyGuardrail стоимость: $0,15 за 1 000 текстовых единиц для фильтров контента или запрещенных тем, $0,10 для фильтров PII или contextual grounding. Текстовая единица — это до 1 000 символов.

Azure AI Content Safety

Поставляется с Prompt Shields — единым endpoint, который «обнаруживает и блокирует атаки на входные данные пользователя… прямые и косвенные угрозы». Azure также открыто сообщает: «Azure AI Content Safety нельзя использовать для обнаружения незаконных изображений сексуальной эксплуатации детей», а качество для многоязычных сценариев ограничено восемью оцененными языками.

OpenAI Moderation и OpenAI Guardrails

omni-moderation-latest — бесплатная мультимодальная базовая проверка. Отдельно openai-guardrails-python (документация: guardrails.openai.com) — это вариант фреймворка от OpenAI: трехэтапный пайплайн (pre-flight, input, output) с Jailbreak Detection, Hallucination Detection через FileSearch, NSFW, PII через Presidio и LLM-as-judge. GuardrailAgent подключается к Agents SDK.

# No-run: illustrative OpenAI Guardrails API shape; requires the package and guardrail_config.json.
from guardrails import GuardrailsOpenAI, GuardrailTripwireTriggered

client = GuardrailsOpenAI(config="guardrail_config.json")
try:
    resp = client.responses.create(model="<your-model-id>", input="...")
except GuardrailTripwireTriggered as e:
    print(f"blocked: {e}")

Что не решают фильтры контента

Для всех семи вариантов актуальны два наблюдения.

Во-первых, опубликованных показателей латентности и throughput мало. Bedrock, Azure и Lakera публикуют цены, но не гарантируют worst-case latency. Meta также не дает гарантии для hosted endpoint Llama Guard. NVIDIA поставляет NeMo Guardrails как ПО, которое вы размещаете самостоятельно, поэтому латентность зависит от вашей модели и инфраструктуры. Измеряйте каждую синхронную проверку на критическом пути, а не выводите ее стоимость из цены продукта.

Во-вторых, этот раздел охватывает перечисленные выше конфигурации, ориентированные на контент.

Фильтр контента может проверять вход и выход модели. Он не показывает, что конкретный tool call авторизован, что MCP-сервер аутентифицировал вызывающую сторону или что система может остановить многошаговую эксфильтрацию данных или выполнение кода до вызова модели. Авторизация — отдельное решение о том, может ли эта идентичность выполнить данный вызов к этому серверу.

NeMo также документирует execution rails и проверку tool calls, а Lakera описывает рантайм-применение политик в отдельном продукте AI Agent Security. Это дополнительные контроли, которые нужно настроить и протестировать. В остальной части статьи рассматриваются проверки вокруг выполнения инструментов.


Угрозы безопасности ИИ-агентов: семь инцидентов и OWASP ASI Top 10

Разрыв между фильтрацией текста и защитой выполнения перестал быть академическим в середине 2025 года. Семь инцидентов ниже затронули retrieval, конфигурацию, учетные данные, установку пакетов или выполнение в CI. Классификатор контента все еще может обнаружить подозрительную строку, но контроли, напрямую блокирующие эти пути, находятся на границах инструментов, идентификации, сэндбоксов и supply chain.

EchoLeak — CVE-2025-32711

Об уязвимости сообщила в июне 2025 года Aim Labs, исследовательское подразделение Aim Security, в Microsoft 365 Copilot. Техническое описание теперь опубликовано на сайте Cato Networks, купившей эту команду, под авторством бывшего руководителя Aim Labs Itay Ravia (описание). Сформулированное как инструкции для человека-получателя, специально созданное письмо прошло мимо XPIA — встроенного фильтра Microsoft, который ищет атаки промпт-инъекции во входных данных Copilot. Затем письмо попало в retrieval-слой Copilot — часть системы, которая ищет контекст для ответов в ваших документах. Исследователи называют этот прием RAG-spraying: атакующий использует несколько писем или длинное письмо, разбитое на чанки, чтобы расширить область retrieval. Это повышает вероятность retrieval, но не гарантирует его. Оказавшись внутри, Copilot послушно встроил наиболее чувствительные данные из сессии в Markdown-ссылку на изображение в домене под контролем атакующего. API предпросмотра Teams, работавший в домене, которому уже доверяли собственные браузерные политики Microsoft, автоматически запросил этот URL изображения и тем самым передал данные атакующему. Ноль кликов. Aim Labs назвала этот класс атак «LLM Scope Violation»: модель пересекает границу, которую никогда не должна была пересекать, используя только операции, считавшиеся легитимными каждой отдельной системой.

Каждый шаг по отдельности выглядел легитимным. Письмо было адресовано человеку. Retrieval получил документ, который должен был получить. Markdown-ссылка отобразилась так, как и должна отображаться Markdown-ссылка. Запрос изображения ушел в allowlist-домен. Исследователи обошли проверку XPIA, а модель последовала косвенным инструкциям из retrieved-контента. Этот случай сочетает сбой промпт-инъекции с поведением retrieval, рендеринга и исходящего трафика; он не показывает, что детекторам нечего было обнаруживать.

Amazon Q Developer VS Code v1.84.0 — июль 2025 года

AWS выпустила скомпрометированную сборку после того, как атакующий закоммитил вредоносный файл системного промпта через чрезмерно широкие права GitHub-токена CodeBuild (advisory). Payload пытался изменить инструкции агента, направив его к разрушительным действиям. Вредоносный код распространялся с v1.84.0, но не выполнился из-за синтаксической ошибки. AWS отозвала учетные данные, удалила код и выпустила v1.85.0. Payload не сработал из-за синтаксической ошибки, а не потому, что его заблокировал какой-либо контрольно-защитный механизм.

Сервис Azure Web Apps MCP — CVE-2026-32211

Запись CVE от поставщика Microsoft касается отсутствия аутентификации в размещенном сервисе Azure Web Apps MCP. Это не advisory против каждого локального Azure MCP-сервера или SDK. Вызывающая сторона, достигшая неаутентифицированного сервиса инструментов, может полностью обойти модель; развернутый сервис должен аутентифицировать и авторизовать запрос.

Уязвимости доверия к проекту в Claude Code

Это отдельные уязвимости, а не обязательные шаги одной атаки:

  1. Обход предупреждения о доверии исправлен в версии 1.0.87.
  2. Выполнение до установления доверия, CVE-2025-59536, исправлено в версии 1.0.111. Конфигурация репозитория могла запустить выполнение до того, как проекту было присвоено доверие.
  3. Утечка endpoint/API-ключа, CVE-2026-21852, исправлена в версии 2.0.65. Непроверенная конфигурация могла перенаправить API-трафик и раскрыть учетные данные.

Доверие к проекту, выполнение хуков и конфигурация endpoint — это контроли хоста. Классификатор контента не может предотвратить выполнение кода до вызова модели.

Axios 1.14.1 и 0.30.4 — 31 марта 2026 года

В postmortem сопровождающего описаны два вредоносных релиза — 1.14.1 и 0.30.4, содержащих зависимость plain-crypto-js@4.2.1. Эта зависимость устанавливала remote access trojan — вредоносное ПО, которое дает атакующему удаленный доступ к машине. Для атаки требовалось разрешить затронутые версии и выполнить соответствующее поведение установки; независимый npm install не загружал их автоматически. Это сбой выполнения в supply chain, не связанный с поведением модели.

Захват тегов Trivy Actions — 19 марта 2026 года

В advisory Aqua описано перенаправление 76 из 77 тегов версий trivy-action и семи тегов setup-trivy на вредоносный контент. Entry point вредоносного action собирал память процесса runner и файлы с учетными данными; не следует приписывать весь этот путь сбора бинарнику сканера. Последующий инцидент с Docker Hub имел отдельное окно воздействия.

Workflow, разрешивший затронутый тег во время компрометации, мог выполнить payload. Теги — изменяемые ссылки, поэтому фиксируйте проверенные Actions на неизменяемые commit SHA и проверяйте изменения зависимостей. Coding agent может распространить ту же небезопасную ссылку в другие workflow-файлы.

OpenAI / Hugging Face — эвалуации в июле 2026 года

В отчете OpenAI об инциденте от 26 августа описано, как внутренние агенты для оценки кибербезопасности получили доступ в интернет через общую инфраструктуру, взаимодействовали через неавторизованную доску сообщений и скомпрометировали системы Hugging Face. Основная модель была доступна только внутри компании, а эвалуации проходили с ослабленными мерами защиты. Это свидетельство о той среде эвалуации, а не измеренная частота сбоев публично развернутых агентов.

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

Асинхронное вмешательство провайдера

Система мониторинга misalignment OpenAI может вмешиваться после вывода или действий. Для охватываемых моделей запросы Responses с сохраненным reasoning, WebSockets или OpenAI compaction могут быть остановлены автоматически. Другие запросы Responses могут создавать алерты без автоматической остановки; Chat Completions не входит в эту систему. Webhook не позволяет блокировать запрос.

Заблокированный запрос возвращает misalignment_policy_violation, а до начала стриминга получает HTTP 403; ошибки также могут прийти в середине стрима. Остановите дальнейшие действия, сохраните записи запросов и инструментов и передайте ситуацию оператору. Не повторяйте запрос автоматически. Предыдущие эффекты сохраняются, а алерты могут срабатывать ошибочно или пропускать проблемы. Это добавляет детекцию, но не заменяет локальную авторизацию.

OWASP ASI Top 10, редакция 2026 года

Agentic Security Initiative (ASI) от OWASP — рабочая группа, сфокусированная именно на агентах под управлением LLM. 9 декабря 2025 года она опубликовала Agentic Security Initiative Top 10 for 2026 — каталог из десяти категорий рисков безопасности агентов.

Используйте его как чеклист покрытия модели угроз, а не как рейтинг измеренной частоты инцидентов:

OWASP ASI Top 10 for 2026OWASP ASI Top 10 for 2026

Фильтры контента могут помогать обнаруживать вредоносные инструкции, включая hijack цели и отравление памяти. Категории пересекаются: ни одна из них не относится исключительно к текстовому фильтру. Сопоставляйте каждый путь атаки с контролями идентификации, политик инструментов, памяти, оркестрации, мониторинга и supply chain по необходимости. EchoLeak соответствует ASI01. Amazon Q — ASI04 (Supply Chain) и ASI02 (Tool Misuse). Azure MCP — ASI03 (Identity). CVE-2025-59536 в Claude Code охватывает ASI05 (Code Execution), ASI04 и ASI03. Axios и Trivy относятся к ASI04. Это сопоставление показывает, почему модель угроз должна выходить за пределы входа и выхода модели.


Разрешения — это инфраструктура, а не промпт

Здесь гардрейлы перестают быть продуктом и становятся одной подсистемой харнесса. Три современные системы — OpenAI Agents SDK, Codex CLI и Claude Code — показывают, как на самом деле выглядит поверхность политик в production. Все три применяют разрешения в коде. Ни одна не полагается на осторожность модели.

OpenAI Agents SDK

SDK разделяет харнесс и compute. Hosted MCP-инструменты принимают require_approval — либо строку "always" / "never", либо filter object с ключами этих двух политик и именами инструментов, на которые распространяется каждая из них, — плюс callback on_approval_request, который вызывается для каждого инструмента, оставшегося под "always", и возвращает {"approve": bool} с необязательной причиной. Детальная фильтрация инструментов (tool_filter) доступна в вариантах локального сервера (MCPServerStdio, MCPServerStreamableHttp, MCPServerSse), если она вам нужна:

# No-run: illustrative Agents SDK shape; requires openai-agents, a configured hosted MCP server, and credentials.
from agents import Agent, HostedMCPTool

def approve(request):
    # Only tools under the "always" policy reach this callback.
    if request.data.name == "delete_repo":
        return {"approve": False, "reason": "escalate to a human reviewer"}
    return {"approve": True}

agent = Agent(
    name="Ops",
    tools=[HostedMCPTool(
        tool_config={
            "type": "mcp",
            "server_label": "github",
            "server_url": "https://mcp.example.com",
            "require_approval": {
                "always": {"tool_names": ["delete_repo"]},
                "never": {"tool_names": ["list_issues"]},
            },
        },
        on_approval_request=approve,
    )],
)

Callback одобрения — это код. Политика одобрения для конкретного инструмента — это код. Этот файл можно прочитать, протестировать и сравнить diff-ом. Нельзя сделать то же самое с системным промптом, который говорит: «пожалуйста, будьте осторожны с production».

Codex CLI и управляемый слой политик

Coding harness OpenAI поддерживает управляемый файл requirements.toml, который IT-отделы могут распространять через управление устройствами. В Unix-системах системный файл находится в /etc/codex/requirements.toml. Он действует как слой жестких ограничений, поэтому настройки уровня проекта не могут переопределить его правила:

# /etc/codex/requirements.toml
[rules]
prefix_rules = [
    { pattern = [{ token = "rm" }, { any_of = ["-rf", "-fr"] }], decision = "forbidden", justification = "Recursive force-delete prohibited by IT policy" },
]

prefix_rules.decision принимает только "prompt" или "forbidden", но никогда "allow". Проект не может выдать себе разрешение, запрещенное управляемым слоем. Allowlists MCP используют и имя, и идентичность — например, строку команды или URL. Поэтому проект не может заявить, что он github-mcp, и указать сервер атакующего. Набор поддерживаемых требований зависит от клиента и версии. В актуальной документации специально указано, что для управляемых ключей профиля разрешений нужен Codex 0.138.0 или новее, поэтому перед rollout тестируйте политику требований на каждой версии клиента в парке.

Лестница разрешений Claude Code

Claude Code не публикует одну фиксированную последовательность из шести проверок для каждого tool call. Его правила разрешений оцениваются deny → ask → allow; первое совпавшее правило определяет результат. Хук PreToolUse запускается до запроса разрешения. Хук может заблокировать вызов, но результат хука не обходит совпавшее правило deny или ask. Активный режим разрешений обрабатывает вызовы, которые правила не разрешили. В Claude Agent SDK есть отдельный callback canUseTool для неразрешенных запросов. Это контроль SDK, а не проверка разрешений Claude Code CLI.

Порядок оценки разрешений Claude CodeПорядок оценки разрешений Claude Code

Режимы переключаются через default → acceptEdits → plan с помощью Shift+Tab. auto, bypassPermissions и dontAsk активируются при определенных условиях входа, которые может заблокировать слой корпоративной управляемой политики. Это больше, чем проверка корректности config-файла. Это машина состояний с правилами приоритета, опубликованная так, чтобы команда безопасности могла рассуждать о ее поведении.

Три радиуса поражения в одном файле

Вот как выглядит конфигурация разрешений в стиле Codex с настройками по умолчанию и двумя именованными профилями:

# ~/.codex/config.toml
approval_policy = "on-request"
sandbox_mode = "workspace-write"

[profiles.ci]
approval_policy = "never"
sandbox_mode = "read-only"

[profiles.release]
approval_policy = "untrusted"
sandbox_mode = "danger-full-access"

[mcp_servers.github]
command = "gh-mcp"
args = ["--readonly"]

Работу выполняют два независимых ключа. approval_policy решает, когда нужно спросить человека. on-request позволяет агенту эскалировать запрос, если он уперся в ограничение. never вообще ничего не спрашивает. untrusted останавливает каждую команду, которой нет в доверенном списке. sandbox_mode определяет, к чему команда может обращаться, если она запущена.

CI никого не прерывает и не может выполнять запись. Release может получить доступ ко всей машине, но почти для всего сначала должен получить подтверждение человека. Профиль release платит за такой охват: danger-full-access отключает сэндбокс, поэтому одобрение untrusted остается единственным действующим контролем. Все, чего нет в доверенном списке, либо проходит через человека, либо не запускается. Теперь этот список — вся граница безопасности.

Профили по умолчанию и CI сохраняют защиту ядра: Seatbelt в macOS, bubblewrap плюс seccomp в Linux и ограниченные токены в Windows. В любом случае мнение модели в это не входит.

Применение сэндбокса — вопрос ОС

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

ПлатформаClaude CodeCodex CLI
macOSSeatbelt через sandbox-exec с профилем SBPL (Seatbelt Profile Language)Seatbelt через sandbox-exec -p
Linuxbubblewrap + socat network proxybubblewrap + seccomp (legacy Landlock через use_legacy_landlock)
WindowsТребуется WSL2Нативные restricted tokens + workspace ACLs + capability SIDs

Там, где ОС предлагает один вариант (Seatbelt, bubblewrap), системы совпадают; там, где нет, расходятся. Сэндбокс Claude Code в Windows требует WSL2; это ограничение сэндбокса, а не утверждение, что CLI не может работать нативно. Codex поставляется с нативным сэндбоксом Windows. В любом случае применение ограничений происходит в ядре, а не в модели.

Linux-путь Codex накладывает вокруг команды три блокировки на уровне ядра. PR_SET_NO_NEW_PRIVS не дает процессу получить дополнительные привилегии, даже если он попытается это сделать. Фильтр seccomp заставляет ядро полностью отклонять целые классы системных вызовов. Сетевые ограничения зависят от того, отключена ли сеть или настроен proxy mode; они не сводятся универсально только к Unix-сокетам. См. реализацию сэндбокса Linux. Новый изолированный /proc скрывает остальную часть машины.

Codex также усиливает защиту собственного бинарника при старте на каждой Unix-платформе. Он устанавливает RLIMIT_CORE=0, чтобы подавить crash dump, и запрещает подключение отладчика. Это другая граница, не та же самая, что сэндбокс.

У сэндбокса Windows есть два режима. unelevated использует restricted token пользователя и не предоставляет сетевое применение ограничений из elevated mode. elevated использует выделенных пользователей сэндбокса, правила firewall и файловые ACL.

Когда сеть отключена, Codex размещает заглушки .bat и .cmd для ssh и scp в каталоге, стоящем в начале PATH. Эти команды завершаются с ненулевым кодом, а не обращаются к настоящим бинарникам. Codex также направляет HTTP_PROXY, HTTPS_PROXY, ALL_PROXY и переменные Git proxy на неработающий локальный порт. Эти меры влияют на кооперативные инструменты; одни только proxy-переменные и заглушки команд не могут ограничить произвольный сетевой доступ.

У сэндбокса также должен быть определенный режим сбоя. В текущей конфигурации Claude Code sandbox.failIfUnavailable: true останавливает выполнение, если сэндбокс не удалось запустить. allowUnsandboxedCommands: false отключает escape hatch для повторной попытки без сэндбокса, но excludedCommands все равно обходит это ограничение. Тестируйте эффективную конфигурацию при отсутствии зависимостей и при обращении к запрещенному назначению.

Брокеринг учетных данных теперь доступен и локально. sandbox.credentials в Claude Code может запрещать именованные файлы и переменные окружения. Маскирование переменных окружения, доступное с v2.1.199, передает сэндбоксированной команде placeholder и позволяет прокси подставить настоящее значение в запросы к настроенным хостам. Укажите узкие injectHosts и настройте TLS termination; маскирование не авторизует запрошенную операцию. Эти контроли охватывают сэндбоксированные Bash-команды, поэтому хукам, MCP-процессам и другим путям выполнения нужна собственная политика учетных данных.

Варианты изоляции помимо Claude Code и Codex

Если вы создаете собственного агента, слово «сэндбокс» оказывается собирательным термином. Open-source варианты лежат на спектре — от легковесных оберток над namespace до полноценных microVM, — и выбор зависит от того, насколько вы доверяете коду внутри.

Легкая изоляция — общее ядро, меньше привилегий:

  • bubblewrap — низкоуровневый конструктор сэндбоксов на базе namespace, используемый Flatpak и Claude Code в Linux. Вызывающая сторона должна сама выбрать файловую систему, сеть и необязательную политику seccomp; сам bubblewrap не является готовой политикой безопасности.
  • Стандартные Docker / OCI-контейнеры — изоляция namespace поверх общего ядра хоста. Это не сэндбокс для непроверенного кода; собственная документация gVisor прямо говорит об этом («containers are not a sandbox»). Это разумная отправная точка в сочетании с seccomp и AppArmor, но не более того.

Изоляция на уровне ядра приложения — агент взаимодействует с фиктивным ядром:

  • gVisor — пользовательское ядро Google. Контейнер считает, что работает в Linux, а реализация ядра на Go перехватывает системные вызовы. Это снижает прямую экспозицию ядра хоста без guest VM, но требует компромиссов по совместимости и производительности.

Полная изоляция VM — отдельное ядро для каждого сэндбокса:

  • Firecracker — технология microVM от AWS. У каждой VM собственное ядро Linux под KVM; контейнеры используют общее ядро хоста. Компрометации одной VM все еще нужно пройти через VMM или контроли хоста, чтобы повлиять на хост или другую VM, поэтому Firecracker Jailer и пропатченный хост остаются частью защиты.
  • Kata Containers — UX контейнеров и изоляция уровня VM. Такой вариант выбирают Kubernetes-кластеры, которым нужно запускать непроверенный код.

Платформы — что можно арендовать вместо самостоятельной разработки:

  • E2B оборачивает Firecracker в hosted sandbox API.
  • OpenSandbox отделяет SDK от настроенной администратором изоляции рантайма. Docker runc по умолчанию не является microVM; путь Firecracker использует Kata с Firecracker через Kubernetes.
  • Agent Governance Toolkit Microsoft (лицензия MIT, апрель 2026 года) добавляет поверх этого движок политик рантайма. Он сопоставляет контроли политик с OWASP ASI Top 10. Заявленная латентность запуска относится к одному движку политик, а не к полной стоимости всех проверок в развернутом агенте.

Выбирайте уровень изоляции с учетом доверия к коду, границы между tenants, сетевого доступа, данных на хосте и стоимости восстановления. Namespace и seccomp могут подходить для доверенных внутренних инструментов. Код, сгенерированный LLM, и непроверенные пакеты требуют более сильной границы — например, gVisor, Kata или microVM, — а затем тестирования путей escape и эксфильтрации в собственной модели угроз.

Claude Code и Codex выбрали то же меню, что и все остальные. Они лишь обернули его по-разному.


Хуки PreToolUse как программируемая политика

Режимы и allowlist подходят для простых случаев: «разрешить агенту редактировать файлы, но запретить запуск bash», «запретить все, что похоже на rm -rf». Они не справляются, когда политике нужна настоящая логика. Например, вы хотите блокировать git push только тогда, когда ветка равна main. Или запрещать любой Edit, затрагивающий файл, совпадающий с regex для секрета. Или ограничивать число shell-вызовов на сессию, либо отправлять каждый вызов инструмента в центральный audit log (SIEM — security information and event management, система управления информацией и событиями безопасности, которую уже отслеживает ваша команда безопасности).

Все это не помещается в статический allowlist. Для этого и нужны хуки — shell-команды, которые Claude Code запускает в определенных точках жизненного цикла tool call. Они могут проверять ожидающий вызов и возвращать структурированное allow/deny. Claude Code предоставляет около тридцати событий жизненного цикла (полный список есть в документации), и одно из них меняет порядок всего остального: хук PreToolUse, возвращающий permissionDecision: "deny", блокирует инструмент независимо от режима.

Форма настроек выглядит так:

{
    "permissions": {
        "defaultMode": "acceptEdits",
        "deny": ["Bash(rm -rf:*)", "Bash(sudo:*)", "Read(.env*)"]
    },
    "hooks": {
        "PreToolUse": [
            {
                "matcher": "Bash",
                "hooks": [
                    {
                        "type": "command",
                        "command": ".claude/hooks/pre-bash-firewall.sh"
                    }
                ]
            },
            {
                "matcher": "Edit|Write",
                "hooks": [
                    {
                        "type": "command",
                        "command": ".claude/hooks/protect-paths.sh"
                    }
                ]
            }
        ]
    }
}

Статические правила deny и динамические хуки имеют разные режимы сбоя. Результат PreToolUse со значением deny блокирует вызов до обычного потока разрешений, но timed-out command-, HTTP- или MCP-tool-хук не блокирует: Claude Code продолжает выполнение этого потока. Поэтому в данном примере acceptEdits может одобрить Edit или Write, если protect-paths.sh завершился по тайм-ауту. Не делайте жесткое ограничение пути зависимым только от command hook. Статические ограничения помещайте в deny-правила или сэндбокс; для динамической политики выбирайте контроль, при сбое эвалуатора которого сохраняется restrictive-поведение, и тестируйте как тайм-аут, так и явный deny.

Хук может быть пятистрочным shell-скриптом или полноценным движком политик. Важна форма результата:

{
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "deny",
        "permissionDecisionReason": "writes outside workspace prohibited"
    }
}

Модель видит структурированный deny. Цикл ризонинга из части 1 обрабатывает его как любое другое наблюдение инструмента: отказ становится контекстом, агент перестраивает план, цикл продолжается. В этом и заключается преимущество идеи «разрешения — это инфраструктура». Deny подключен к тому же механизму, который обрабатывает HTTP-ошибку 500 от инструмента. Не требуется отдельный security workflow, который нужно пристегивать сбоку.

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


Одобрение человеком работает только как эскалация

Фильтры контента проверяют, что говорит модель. Правила разрешений проверяют tool call до его выполнения. Human-in-the-loop review обрабатывает действия, для которых все еще нужен человек. Если люди одобряют 93% запросов, проверьте правила эскалации и качество самих запросов.

LangGraph предоставляет примитив pause/resume. HumanLayer упаковывает канал одобрения, а данные использования Anthropic показывают, почему нужно измерять количество и качество эскалаций.

Примитив LangGraph

Связка interrupt() + Command(resume=value) в LangGraph приостанавливает граф, сохраняет его состояние через настроенный checkpointer и возобновляет работу со значением, предоставленным человеком. Безопасность resume зависит от одной детали в документации:

«Когда выполнение возобновляется (после предоставления запрошенного ввода), рантайм перезапускает весь node с начала — он не продолжает выполнение с точной строки, где был вызван interrupt».

Из этого поведения перезапускают три ограничения:

1. Побочные эффекты до interrupt() должны быть идемпотентными. Когда человек отвечает, весь node запускается снова с начала, а не со строки interrupt(). Поэтому если node отправляет письмо, приостанавливается для одобрения, а затем возвращает «отправлено», после resume письмо уйдет повторно. Исправление: помещайте побочные эффекты после interrupt или делайте их безопасными для повторения (dedupe keys, upsert вместо insert, кэширование по ID сообщения).

2. Interrupt сопоставляются с resume по индексу, а не по имени. Если в одном node есть два вызова interrupt(), LangGraph сопоставляет их со значениями Command(resume=...) в порядке срабатывания. Любое ветвление, меняющее число interrupt (например, if, пропускающий один interrupt при resume, или цикл с другим числом итераций), нарушит соответствие индексов, и значение resume может попасть не к тому interrupt.

3. Payload должны быть безопасны для JSON. Документация LangGraph требует JSON-сериализуемых значений для interrupt() и payload resume. Используйте строки, числа, boolean, массивы и словари, содержащие такие значения. Избегайте функций, экземпляров классов и других сложных объектов, поскольку сериализация зависит от настроенного checkpointer. Преобразуйте данные одобрения в словари и примитивы до передачи в interrupt() или публикации через HTTP API.

Три канонических паттерна:

# No-run: illustrative LangGraph sketches; requires LangGraph, a tool decorator, interrupt, and smtp_send.
# (a) Approval check
@tool
def send_email(to, subject, body):
    resp = interrupt({"action": "send_email", "to": to,
                      "subject": subject, "body": body})
    if resp.get("action") == "approve":
        return smtp_send(to, subject, body)
    return "Email cancelled"

# (b) Edit-and-continue
def review_node(state):
    edited = interrupt({"content": state["generated_text"]})
    return {"generated_text": edited}

# (c) Mid-run state correction — conditional edge
class AgeState(TypedDict):
    age: int | None
    pending_question: str | None

def get_age_node(state: AgeState):
    question = state.get("pending_question") or "What is your age?"
    answer = interrupt(question)  # once per node invocation
    if isinstance(answer, int) and answer > 0:
        return {"age": answer, "pending_question": None}
    return {"pending_question": f"'{answer}' is not valid. Please enter a positive number."}

def route_age(state: AgeState):
    return END if state.get("age") is not None else "get_age"

builder = StateGraph(AgeState)
builder.add_node("get_age", get_age_node)
builder.add_edge(START, "get_age")
builder.add_conditional_edges("get_age", route_age)

Resume — это graph.invoke(Command(resume={"action": "approve"}), config=cfg). LangGraph 0.4+ поддерживает resume на основе словаря для нескольких interrupt в параллельных ветках, что становится важным сразу после fan-out агента.

HumanLayer: одобрение как продукт

HumanLayer — управляемая версия той же идеи. Вы декорируете функцию, а запросы на одобрение направляются в Slack, email или Discord с правилами, определяющими, кого уведомлять. Когда агент пытается вызвать multiply(2, 5), логи выглядят так:

last message led to 1 tool calls: [('multiply', '{"x":2,"y":5}')]
HumanLayer: waiting for approval for multiply

Одобряющий нажимает approve или deny в Slack. При отказе документация HumanLayer формулирует это так: «HumanLayer передаст ваш отзыв агенту, который затем сможет скорректировать свой подход». Благодаря такому отзыву агент может пересмотреть план, а не воспринимать отказ как тупик.

Усталость от одобрений в данных

Anthropic опубликовала реальные данные в феврале 2026 года. Важнее всего три результата.

«Мы обнаружили, что 80% tool calls исходят от агентов, у которых, судя по всему, есть хотя бы один тип защиты (например, ограниченные разрешения или требования одобрения человеком), у 73% каким-либо образом присутствует human-in-the-loop, и только 0,8% действий выглядят необратимыми».

Это хорошая новость. Считайте 80% верхней границей, поскольку в сноске 14 Anthropic добавляет: «Claude часто переоценивал степень участия человека, поэтому мы ожидаем, что 80% — это верхняя граница».

«Новые пользователи (<50 сессий) используют full auto-approve примерно в 20% случаев; после 750 сессий этот показатель превышает 40% сессий».

Это и есть drift. Пользователи начинают осторожно, а затем становятся менее осторожными по мере роста доверия к инструменту. Так ведут себя люди, и это не недостаток характера. Это телеметрический сигнал, который должна отслеживать система. (Небольшое замечание по проверке фактов: во вторичных публикациях этот показатель часто цитировали как «20% → больше 50%». Согласно первичным данным Anthropic, подтвержденное значение — 20% → больше 40%. Если вы видели цифру 50%, вот ее источник.)

В инженерной статье Anthropic о auto mode Claude Code от марта 2026 года приведено ключевое число:

«Пользователи Claude Code одобряют 93% запросов на разрешения. Мы создали классификаторы, чтобы автоматизировать часть решений, повысить безопасность и снизить усталость от одобрений… Если за сессию накапливается 3 последовательных отказа или 20 отказов всего, мы останавливаем модель и эскалируем ситуацию человеку».

Высокая доля одобрений — повод проверить, помогают ли людям запросы принять решение; это не доказательство универсального порога сбоя. Двухэтапный классификатор Anthropic снижает число запросов и эскалирует сгруппированные отказы. Заявленная доля false positive 0,4% рассчитана на 10 000 примеров трафика. Доли false negative составили 17% на 52 реальных чрезмерно активных действиях и 5,7% на 1 000 синтетических случаях эксфильтрации. Это разные выборки, а не взаимодополняющие проценты и не доказательство того, что классификация заменяет сэндбоксинг.

Измеряйте качество эскалации

Помещайте рутинные обратимые действия в allowlist и логируйте их. Эскалируйте действия, побочные эффекты которых пересекают границу, неустранимую рантаймом, — например, внешнее сообщение, запись в production, force push или платеж. Anthropic формулирует цель так: человек должен сохранять возможность вмешаться, когда решение имеет реальные последствия.

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


Ограничение области MCP и supply chain

MCP подключает агентов к внешним инструментам, таким как Slack, GitHub и базы данных, поэтому его модель авторизации становится частью границы безопасности. Ревизии спецификации 2025 года разделили роли issuer токенов и resource server и добавили resource indicators. Эта история объясняет, какие проверки audience и forwarding сервер должен выполнять сегодня.

Авторизация MCP в трех ревизиях

В спецификации 2025-03-26 авторизация была необязательной для реализаций MCP. Для production-деплоя по HTTP, защищающего пользовательские данные или инструменты, я рекомендую OAuth 2.1 с PKCE (Proof Key for Code Exchange), который спецификация требует при поддержке OAuth-авторизации. Ранняя архитектура позволяла одному MCP-серверу выполнять две роли. Authorization server выдает токены; resource server принимает их. Это разные роли, даже если обе выполняет один сервис. Если такой сервис пересылает запрос другому серверу, те же учетные данные могут уйти туда, куда не предназначались. В этом и состоит уязвимость.

Ревизия 2025-06-18 явно разделила роли. Защищенный MCP-сервер выступает как OAuth resource server, а authorization server выдает токен. Authorization server может быть размещен вместе с resource server или отдельно. Resource Indicators RFC 8707 привязывают токен к целевому ресурсу, а Protected Resource Metadata RFC 9728 предоставляет клиенту явный путь discovery. Спецификация также запрещает MCP-серверу пересылать токен клиента upstream.

Ревизия 2025-11-25 сохранила это разделение и доработала части, которые должен корректно реализовывать клиент. Discovery authorization server получил OpenID Connect Discovery, поэтому клиент может найти правильный issuer, а не угадывать его. Incremental scope consent переместился в заголовок WWW-Authenticate, что позволяет серверу запросить дополнительный scope в момент необходимости, а не требовать все заранее. Регистрация клиента получила OAuth Client ID Metadata Documents как рекомендуемый механизм, заменив dynamic registration для большинства деплоев. Discovery Protected Resource Metadata также был приведен в соответствие с RFC 9728, сделав WWW-Authenticate необязательным с fallback через .well-known.

Перед реализацией проверьте страницу версионирования. По состоянию на 6 сентября 2026 года текущая ревизия — 2026-07-28. Она требует, чтобы каждый запрос объявлял версию протокола, и позволяет серверу принимать или отклонять запросы независимо. Клиент может вызвать server/discover, чтобы заранее выбрать версию, но discovery необязателен. Объявление и negotiation для каждого запроса остаются обязательными, в том числе когда клиент обрабатывает ошибку неподдерживаемой версии и повторяет запрос с взаимно поддерживаемой версией.

Привязка к audience ограничивает replay против неправильного MCP-сервера. Она не устраняет отдельные уязвимости конфигурации Claude Code, описанные выше: host-side hook все еще может выполниться до запуска модели, а непроверенный проект все еще может попытаться изменить локальную конфигурацию. Scope токена, доверие к проекту, политика хуков и сэндбоксинг остаются отдельными контролями.

Чеклист MCP на 2026 год

Если вы выпускаете или используете MCP в production:

  1. Считайте аутентификацию production-требованием, а не default протокола. MCP оставляет авторизацию необязательной, но для защищенного HTTP-деплоя я рекомендую OAuth 2.1 с PKCE. Advisory по Azure Web Apps MCP касался отсутствия аутентификации. Если ваш сервер принимает трафик без проверки учетных данных вызывающей стороны, вы создали инструмент, который может вызвать любой, кто до него доберется.
  2. Токены привязаны к audience. Запрашивайте токен для целевого MCP-ресурса и проверяйте, что представленный токен указывает ваш сервер как audience. Отклоняйте токены, выпущенные для другого ресурса.
  3. Разделяйте права чтения и записи осознанно. MCP привязывает токен к resource server, а не к отдельному инструменту. Если Slack-сервер принимает credential с chat:write и маршрутизирует его и в read-, и в write-handler, инструмент, ориентированный на чтение, может превратиться в путь отправки сообщений через политику этого сервера. Используйте отдельные resource server или отдельные учетные данные и проверки авторизации, если для операций чтения и записи нужны независимые радиусы поражения.
  4. Используйте свежие короткоживущие токены вместо постоянных API-ключей. Эталонный паттерн — vault Claude Managed Agents (инженерная статья Anthropic): сам агент никогда не видит реальные учетные данные. Прокси получает соответствующие сохраненные учетные данные из vault, вызывает инструмент от имени агента и возвращает результат. Выпуск свежего токена для каждого вызова не является документированной гарантией; короткоживущие учетные данные — рекомендация для деплоя.

Контроли supply chain по-прежнему действуют

Инциденты axios и Trivy — знакомые сбои supply chain в пакетах и CI, примененные к системам, автоматически устанавливающим зависимости. Автоматизация увеличивает число и скорость выполнений, поэтому проверки версий, provenance и ревью должны срабатывать до того, как сгенерированная команда попадет в CI или сэндбокс.

Защита проста:

  • Фиксируйте версии в lockfile. Агенты никогда не должны разрешать плавающую версию — ни @latest, ни npm update, ни --upgrade.
  • Запускайте сканирование в CI инструментами, независимыми от проверяемого компонента.
  • Используйте GitHub commit SHA для Actions, а не теги.
  • Проверяйте diff зависимостей в PR, созданных агентом, до merge.

Это стандартные контроли supply chain. Агентная автоматизация меняет их частоту, но не механизм.


Стек политик для Market Analyst Agent

Market Analyst Agent из части 1 — небольшой агент LangGraph, который получает рыночные данные и пишет аналитический отчет, но это описание скрывает больше, чем кажется. Помимо инструментов рыночных данных, он запускает allowlisted CLI через subprocess, вычисляет написанный моделью Python внутри процесса и создает записи симулированных сделок. Он проверяет вызов инструментов, выполнение кода и маршрутизацию одобрений, но не размещает реальные ордера. Вот как выглядит минимальный стек политик.

Слой 1: хук PreToolUse, блокирующий до выполнения

Даже агент, который «просто читает данные об акциях», может обратиться не туда: отправить curl на URL под контролем атакующего, записать за пределами workspace или выполнить git-изменения в host-репозитории. Deny-правило — это инфраструктура, а не промпт. Приведенный ниже эскиз возвращает собственную форму решения агента, а не обертку hookSpecificOutput, которую ожидает Claude Code.

# agent/permissions.py
from pathlib import Path

DENY_COMMANDS = frozenset({
    "rm -rf", "sudo", "chmod 777",
    "curl -X POST", "wget", "nc ",
})
WORKSPACE = Path("./workspace").resolve()

def _outside_workspace(path: str) -> bool:
    # Resolve first: "~/.ssh/id_rsa" and "workspace/../../etc" both
    # have to become real paths before the comparison means anything.
    return not Path(path).expanduser().resolve().is_relative_to(WORKSPACE)

def pre_tool_use(tool_name: str, args: dict) -> dict | None:
    if tool_name == "shell":
        cmd = args.get("command", "")
        if any(bad in cmd for bad in DENY_COMMANDS):
            return {"permissionDecision": "deny",
                    "reason": f"command pattern disallowed: {cmd!r}"}
    if tool_name == "write_file":
        path = args.get("path", "")
        if _outside_workspace(path):
            return {"permissionDecision": "deny",
                    "reason": f"path outside workspace: {path!r}"}
    return None  # fall through to mode / canUseTool

Эскиз делает точку контроля видимой. Хук возвращает структурированный deny, а цикл ризонинга получает отказ как наблюдение инструмента.

Проверка пути — это allowlist: один корень workspace, все остальное запрещено. Deny-list запрещенных префиксов блокирует только пути, о которых вы подумали. ~/.ssh/id_rsa никогда не будет записан ровно так, как вы указали. Проверка команд все еще остается deny-list. Сопоставление по подстроке — не production-политика для shell. Реальная реализация должна парсить команду и полагаться на сэндбокс ОС при выполнении. Сам эскиз не является границей выполнения: если его запускает внешний хук, тайм-аут должен оставлять в силе независимое ограничение workspace.

Слой 2: input canary для промпт-инъекции

Hijack цели агента (ASI01) часто приходит через retrieved веб-страницу, сообщение пользователя или PDF исследовательской статьи. Дешевый regex-canary обнаруживает буквальные шаблоны инструкций и создает полезное телеметрическое событие. Он пропустит обфусцированные, многоязычные и зависящие от контекста инъекции, поэтому не может служить границей принятия решения:

# agent/input_canary.py
import re

INJECTION_PATTERNS = [
    re.compile(r"ignore\s+(?:all\s+|any\s+|the\s+)?"
               r"(?:previous\s+|prior\s+|above\s+|earlier\s+)?"
               r"(?:instructions|rules|prompts?)",
               re.IGNORECASE),
    re.compile(r"you are now|act as|roleplay as", re.IGNORECASE),
    re.compile(r"system[ _:]*prompt", re.IGNORECASE),
    re.compile(r"<\|im_(start|end)\|>"),
]

def input_canary(text: str) -> dict | None:
    for pat in INJECTION_PATTERNS:
        m = pat.search(text)
        if m:
            return {"flag": "possible_injection", "match": m.group(0)}
    return None

Логируйте помеченные входные данные, но не отклоняйте их автоматически. Для исследовательского ассистента false positive здесь слишком дороги. Однако именно лог позволяет заметить внезапный всплеск числа флагов у одного пользователя.

Слой 3: валидация structured output через stop hook

Модель Pydantic плюс хук Stop дают плотный цикл «валидация → retry» для генерации отчета. Агент не может заявить «готово», пока вывод не пройдет проверку схемы и smoke test:

# No-run: illustrative policy sketch; requires Pydantic and the repo-local agent.schemas module.
# agent/stop_hook.py
from pydantic import ValidationError
from agent.schemas import MarketReport

def on_stop(final_output: str) -> dict:
    try:
        report = MarketReport.model_validate_json(final_output)
    except ValidationError as e:
        return {"decision": "continue",
                "feedback": f"schema invalid: {e.errors()[:3]}"}
    if not report.tickers:
        return {"decision": "continue",
                "feedback": "no tickers in report — did you skip the snapshot step?"}
    return {"decision": "allow_stop"}

Проверка схемы и один smoke test — это разница между «агент сказал, что закончил» и «вывод действительно является отчетом».

Слой 4: одобрение перед исходящими действиями

execute_trade рыночного аналитика — симулированный идемпотентный переход состояния, поэтому он демонстрирует маршрутизацию одобрения, а не необратимый финансовый побочный эффект. Для реальной outbound-интеграции — email, Slack, отчета клиенту или брокерского ордера — покажите человеку предлагаемое действие и дождитесь одобрения или отказа до запуска инструмента. Для паузы используйте interrupt():

# No-run: illustrative outbound-tool sketch; requires LangGraph, a tool decorator, and smtp_send.
# agent/tools/notify.py
from langgraph.types import interrupt

@tool
def send_report(to: str, body: str):
    resp = interrupt({
        "action": "send_report",
        "to": to,
        "body": body,  # Review the complete content that will execute.
    })
    if resp.get("action") == "approve":
        return smtp_send(to, body)
    return "send cancelled by human"

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

Чего этот стек не делает

Это не защита от:

  • Скомпрометированной upstream-зависимости (класс axios). Агент запускает то, что говорит uv sync.
  • Вредоносного .mcp.json в клонированном репозитории (класс CVE-2025-59536). Это обнаруживается моделью разрешений host MCP-клиента, а не кодом агента.
  • Цепочки кражи данных, построенной из легитимных инструментов (класс EchoLeak): агент читает приватные данные, получает внешние URL и отправляет сообщения наружу. Разрывайте или ограничивайте этот путь эксфильтрации ограниченным доступом к данным, доверенным роутингом, ограничениями egress и обязательным одобрением. Удаление одной возможности блокирует конкретный путь, но не любую возможную атаку.
  • Escape из execute_python_analysis — in-process Python evaluator агента. Он блокирует список типов операторов, отклоняет любой идентификатор, начинающийся с подчеркивания, и разрешает импорты только из json, math и statistics. Но exec внутри worker-процесса не является границей: bypass выполняется с доступом worker к файловым дескрипторам и сети. Перед вычислением непроверенного кода вынесите его за изоляцию файловой системы, сети и учетных данных, а также ограничьте CPU, память и время. Один subprocess наследует доступы и не является security sandbox.

Эти четыре слоя — локальная политика, причем локальная политика является внутренним слоем, который вы контролируете, а не единственным. Каждый пункт из списка должен быть перехвачен где-то еще: в lockfile, MCP-клиенте, границе процесса вокруг сгенерированного кода или контролях, не позволяющих непроверенным инструкциям связать приватные данные с адресатом эксфильтрации.


Ключевые выводы

  1. Фильтры контента и политика выполнения защищают разные границы. Фильтры проверяют вход и выход модели. Авторизация инструментов, scope учетных данных, сэндбоксы и контроли supply chain действуют на путях, использованных в семи инцидентах.
  2. Для большинства категорий OWASP ASI нужны контроли за пределами вывода модели. Используйте список, чтобы сопоставить каждую угрозу с компонентом, который действительно может ее заблокировать или записать.
  3. Разрешения — это инфраструктура, а не промпт. Claude Code документирует приоритет правил deny, ask и allow, а PreToolUse может блокировать выполнение. Claude Agent SDK предоставляет отдельный путь canUseTool. Другим рантаймам нужна столь же тестируемая модель приоритетов.
  4. Считайте структурированный deny хука PreToolUse обычным наблюдением инструмента. Цикл ризонинга уже умеет его обрабатывать. Отдельный security workflow не нужен.
  5. Доля одобрений 93% — сигнал проверить качество промптов и частоту эскалаций. Отслеживайте правки, отказы и инциденты после одобрения, а не копируйте универсальную целевую цифру.
  6. Токены, привязанные к audience, и vault на сессию ограничивают replay и экспозицию учетных данных. Они не заменяют доверие к проекту, политику хуков или сэндбоксинг.
  7. Проверки supply chain должны выполняться со скоростью автоматизации. Фиксируйте версии и SHA для Actions, сканируйте в CI и проверяйте изменения зависимостей в pull request, созданных агентом.
  8. Стройте слой политик так, чтобы запуск нового продукта его не обнулял. OpenAI Agents SDK, Codex CLI и Claude Code выражают одни и те же примитивы по-разному. Ставка должна быть на примитивы — лестницы разрешений, хуки, сэндбоксы, interrupt и токены, привязанные к audience.

Следующий слой — рантайм

В части 5, Long-Running AI Agent Runtime, показано, где во время долгого запуска находятся сэндбокс, брокер секретов, чекпоинт и audit trace. Затем часть 6 перемещается внутрь харнесса, где эта лестница разрешений является одним из нескольких этапов, и рассматривает, как acceptance checks, retry и эвалуация на основе трейсов не дают циклу объявить успех слишком рано. Там добавляется вопрос, который не требовался в этой статье: безопасно ли вообще повторно отправлять вызов, завершившийся по тайм-ауту в процессе выполнения.


Ссылки

Формулировки и концепции

Продукты для гардрейлов LLM

Инциденты

Поверхности политик

HITL

OWASP


Слой политик Market Analyst Agent находится в объединенном графе analysis-to-trade репозитория, а не в графе analysis, перечисленном в части 1. Он управляет состоянием симулированных сделок: детерминированный guardian node отклоняет ограниченные действия, автоматически одобряет действия с низкой стоимостью и эскалирует остальные к node compliance officer, после чего граф останавливается с interrupt_before. Слой политик находится на GitHub. Приведенные выше deny hook, input canary и валидатор Stop-hook — эскизы тех же точек контроля. Они написаны для чтения, а не для прямой вставки в этот репозиторий.