Безопасность агентов
Permission envelope для агента: как ограничить инструменты параметрами, временем и контекстом
Разрешение «агент может вызвать инструмент» слишком широко. Для конкретной задачи полезнее определить, какой инструмент разрешён, с какими аргументами, в течение какого времени и при каких условиях контекста.
Почему разрешения на уровне инструмента недостаточно
Агент, которому целиком доступен инструмент отправки HTTP-запросов, теоретически может обратиться к любому адресу, выбрать любой метод и передать произвольное тело. Но задача «получить статус заказа № 42» требует значительно меньших полномочий: одного домена, метода GET, одного шаблона пути, ограниченного времени и подтверждённого идентификатора заказа.
Permission envelope — это применяемая перед вызовом инструмента совокупность ограничений на имя операции, аргументы, время, объём, происхождение данных и состояние задачи. Это рабочий термин данной статьи, а не ссылка на внешний стандарт.
Ключевое свойство envelope — исполнимость. Ограничение должно проверяться программой на каждом вызове, а не существовать только в системном промпте. Промпт объясняет агенту правила; посредник исполнения принудительно их применяет.
Из чего состоит permission envelope
- Инструмент и операция
- Точное имя инструмента и, если применимо, разрешённый метод: например, только
orders.read. - Параметры
- Схема, допустимые значения, шаблоны строк, числовые диапазоны и запрет неизвестных полей.
- Время
- Момент начала и окончания полномочия, максимальная длительность вызова и срок действия подтверждения.
- Контекст
- Идентификатор задачи, шаг рабочего процесса, субъект, источник аргументов и связанные ресурсы.
- Бюджет
- Число вызовов, объём ответа, стоимость либо количество затрагиваемых объектов.
- Эффект
- Только чтение, обратимое изменение или необратимое действие. Чем сильнее эффект, тем уже envelope.
- Аудит
- Стабильный идентификатор решения, причина разрешения или отказа и безопасный отпечаток аргументов.
Воспроизводимая реализация
Шаг 1. Разложите задачу на конкретные вызовы
Начните не со списка доступных инструментов, а с ожидаемого эффекта. Для примера возьмём задачу: «прочитать статус заказа, идентификатор которого пользователь явно указал в текущей задаче».
Минимальный вызов выглядит так:
{
"tool": "orders.read",
"arguments": {
"order_id": "ord-42",
"fields": ["status"]
}
}
Запись заказа не требуется, чтение других полей не требуется, поиск по всем заказам не требуется. Эти исключения должны быть отражены в политике, а не оставлены на усмотрение модели.
Шаг 2. Опишите envelope как данные
Следующая конфигурация — самостоятельный пример формата. Её поля нужно адаптировать к вашему исполнителю инструментов.
{
"envelope_id": "env-order-status-v1",
"task_id": "task-7f3a",
"subject": "agent-order-assistant",
"not_before": "2026-07-29T09:00:00Z",
"expires_at": "2026-07-29T09:05:00Z",
"tools": {
"orders.read": {
"effect": "read",
"max_calls": 1,
"timeout_ms": 3000,
"max_response_bytes": 16384,
"arguments": {
"additionalProperties": false,
"required": ["order_id", "fields"],
"properties": {
"order_id": {
"type": "string",
"const": "ord-42"
},
"fields": {
"type": "array",
"const": ["status"]
}
}
},
"context": {
"required_workflow_step": "read_order_status",
"order_id_source": "user_message",
"require_task_match": true
}
}
}
}
const здесь намеренно строже регулярного выражения. Если идентификатор уже известен при выдаче полномочия, разрешайте именно его. Шаблон вроде ^ord-[0-9]+$ открыл бы доступ ко всем подходящим заказам.
Шаг 3. Создавайте envelope из доверенного контекста
Не позволяйте агенту самостоятельно объявлять, что аргумент «пришёл от пользователя». Происхождение данных должен отмечать оркестратор при разборе входа.
{
"task_id": "task-7f3a",
"workflow_step": "read_order_status",
"bindings": {
"order_id": {
"value": "ord-42",
"source": "user_message",
"message_id": "msg-19"
}
}
}
Envelope формируется из этой привязки вне модели. Агент получает возможность использовать значение, но не может заменить подтверждённый order_id другим.
Шаг 4. Проверяйте каждый вызов в посреднике
Проверка должна выполняться непосредственно перед передачей управления адаптеру инструмента. Псевдокод ниже показывает порядок проверок:
function authorize(call, envelope, runtime):
deny if runtime.now < envelope.not_before
deny if runtime.now >= envelope.expires_at
deny if runtime.task_id != envelope.task_id
rule = envelope.tools[call.tool]
deny if rule is missing
deny if runtime.workflow_step != rule.context.required_workflow_step
deny if runtime.call_count(call.tool) >= rule.max_calls
validate_exact_schema(call.arguments, rule.arguments)
verify_argument_provenance(call.arguments, rule.context, runtime.bindings)
return permit(
timeout_ms = rule.timeout_ms,
max_response_bytes = rule.max_response_bytes
)
Порядок полезен для предсказуемости: сначала дешёвые проверки срока и задачи, затем выбор правила, после него схема и происхождение данных. При любом несоответствии применяется отказ по умолчанию.
Шаг 5. Ограничьте сам исполнитель
Авторизация вызова не заменяет ограничения среды. Адаптер должен соблюдать переданные пределы времени и размера ответа. Для сетевого инструмента дополнительно фиксируют схему URL, имя узла, метод, путь и запрет перенаправлений.
{
"tool": "http.request",
"max_calls": 1,
"timeout_ms": 3000,
"arguments": {
"method": { "const": "GET" },
"scheme": { "const": "https" },
"host": { "const": "orders.internal.example" },
"path": { "const": "/v1/orders/ord-42/status" },
"follow_redirects": { "const": false },
"body": { "const": null }
}
}
Домен example зарезервирован здесь исключительно как иллюстрация. Команда не предназначена для реального запуска.
Шаг 6. Отделите чтение от изменений
Не объединяйте чтение и запись общим разрешением вроде orders.*. Для изменения задайте отдельный envelope, точное новое значение и одноразовое подтверждение:
{
"tool": "orders.cancel",
"effect": "write",
"max_calls": 1,
"arguments": {
"order_id": { "const": "ord-42" },
"reason_code": { "enum": ["user_requested"] }
},
"confirmation": {
"required": true,
"scope": "cancel:ord-42",
"max_age_seconds": 120,
"single_use": true
}
}
Это пример структуры. Подтверждение должно создаваться доверенным компонентом после явного действия пользователя, а не текстом, который модель сгенерировала сама.
Шаг 7. Записывайте решение без утечки данных
Для расследования нужен журнал решения, но полные аргументы могут содержать чувствительные данные. Записывайте минимальный набор метаданных и отпечаток нормализованных аргументов.
{
"decision": "deny",
"reason": "argument_constraint_failed",
"envelope_id": "env-order-status-v1",
"task_id": "task-7f3a",
"tool": "orders.read",
"failed_path": "$.order_id",
"arguments_digest": "sha256:<digest>",
"timestamp": "2026-07-29T09:02:11Z"
}
<digest> — обозначение вычисляемого значения, а не готовый хеш. Секреты, токены авторизации и полные пользовательские сообщения в такой журнал включать не следует.
Как проверить результат
Проверка строится как таблица решений. Ниже приведены сценарии, которые следует выполнить в собственной тестовой среде; результаты здесь являются ожидаемыми, а не заявлением о запуске конкретной реализации.
| Сценарий | Вызов | Ожидаемое решение |
|---|---|---|
| Разрешённый минимум | orders.read(ord-42, [status]) |
Разрешить один раз |
| Другой объект | orders.read(ord-43, [status]) |
Отказать по ограничению аргумента |
| Лишнее поле | orders.read(ord-42, [status, address]) |
Отказать по точной схеме |
| Лишний аргумент | Добавлен include_history |
Отказать из-за additionalProperties: false |
| Повторный вызов | Тот же вызов второй раз | Отказать по бюджету |
| Истёкшее окно | Вызов после expires_at |
Отказать по времени |
| Другая задача | Тот же вызов с другим task_id |
Отказать по контексту |
| Подмена источника | Идентификатор создан моделью | Отказать по происхождению данных |
Минимальный критерий готовности: разрешённый сценарий проходит, каждый отрицательный сценарий получает детерминированный отказ, а исполнитель не запускается после отказа.
Безопасная локальная проверка конфигурации
Если envelope сохранён как envelope.json, синтаксис JSON можно проверить локально без сетевых запросов:
python3 -m json.tool envelope.json > /dev/null
Успешное завершение подтверждает только корректность JSON. Оно не проверяет семантику политики. Для семантической проверки нужен валидатор, который понимает выбранный вами формат envelope и применяет отказ при неизвестных полях.
Типовые ошибки
Разрешать шаблон вместо конкретного значения
order_id matches ^ord- выглядит ограничением, но разрешает множество объектов. После привязки ресурса заменяйте диапазон точным значением.
Проверять только промпт
Фраза «не вызывай инструмент чаще одного раза» не является счётчиком. Лимит должен храниться и атомарно уменьшаться исполнителем.
Доверять контексту, созданному агентом
Поля approved: true или source: user внутри аргументов не доказывают подтверждение. Метки происхождения и согласия должны поступать из доверенной части системы.
Выдавать полномочие без срока
Envelope, переживший задачу, может быть повторно использован позднее. Привязывайте его одновременно к task_id, шагу процесса и короткому временному окну.
Забывать о косвенных эффектах
Инструмент с названием read может обновлять отметку просмотра, запускать webhook или сохранять кэш. Классифицируйте инструмент по фактическому эффекту адаптера, а не по имени.
Разрешать неизвестные поля
Расширяемая схема удобна, но новый аргумент может неожиданно усилить действие. На границе авторизации используйте закрытые схемы и вводите новые поля через новую версию политики.
Проверять URL одной строкой
Сравнение префикса не защищает от неоднозначного разбора адреса и перенаправлений. Нормализуйте URL, отдельно проверяйте схему, узел, порт и путь, затем повторяйте сетевые ограничения на уровне транспорта.
Ограничения подхода
Permission envelope уменьшает полномочия вызова, но не доказывает безопасность самого инструмента. Ошибка внутри адаптера, неправильная изоляция сети или чрезмерные права сервисной учётной записи остаются отдельными рисками.
Точное ограничение аргументов также не гарантирует безопасный результат, если значение ресурса изменилось между проверкой и выполнением. Для операций записи используйте версии объектов, условные обновления или транзакционные проверки непосредственно в целевой системе.
Короткое временное окно зависит от надёжных часов, а одноразовый бюджет — от атомарного хранилища. В распределённой системе нужно заранее определить поведение при повторной доставке, задержках и недоступности счётчика. Безопасное значение по умолчанию — отказ.
Наконец, слишком узкий envelope может мешать восстановлению после ожидаемой ошибки. Не расширяйте его автоматически: создайте новый envelope для диагностического шага, сохранив отдельный журнал решения.
Итоговый шаблон проверки
Перед выдачей полномочий ответьте на вопросы:
- Какой единственный эффект требуется задаче?
- Какое точное имя инструмента реализует этот эффект?
- Какие аргументы можно заменить константами?
- Какие неизвестные поля должны быть запрещены?
- Откуда доверенно получено каждое значимое значение?
- К какой задаче и стадии процесса относится вызов?
- Когда полномочие начинается и истекает?
- Сколько вызовов, объектов и байтов допустимо?
- Требует ли эффект отдельного подтверждения?
- Как отказать до запуска инструмента и безопасно записать причину?
Если каждое утверждение выражено машинно проверяемым условием, разрешение перестаёт быть общим доступом к инструменту и становится узким полномочием для одного шага задачи.