Назад в блог
AI Agents

Permission model для AI-tools: least privilege на практике

Каждый вызов привязан к задаче и пользователю

Объект и эффект проверяются сервером

Авторская схема PERMIT-7 и синтетический тикет
Ошибки, тесты и production-контроль
ТемаPermission decision gate
ФокусPERMIT-7
СтатусPUBLISHED / 2026-09-26
Схема разграничения доступа к инструментам AI-агента
Схема разграничения доступа к инструментам AI-агента
TERMINAL_PREVIEW.LOG
$ permit --contract PERMIT-7
> bind: actor / tenant / task
> gate: tool / target / effect
> route: allow / review / deny
Разбор

Модель может предложить вызов инструмента, но не должна назначать себе права. Permissions для AI agents — это проверка каждого вызова по подтверждённому пользователю, tenant, задаче, объекту и разрешённому действию. Материал разбирает именно эту границу. Общий запрос на AI-специалиста в Армении ведёт на отдельную страницу.

Ниже — авторская схема PERMIT-7 и синтетический пример поддержки: агент читает один разрешённый тикет и готовит черновик ответа. Это проектная схема, не описание внедрения клиента и не измеренный результат. Смежные материалы: схемы инструментов, безопасность MCP-сервера и sandbox агента.

Проблема и требования

Каталог может содержать read_ticket, update_ticket и send_reply. Общий сервисный токен превращает ошибку prompt в потенциальное чтение чужого tenant или отправку сообщения. Запрет в prompt не заменяет проверку в целевой системе. Сервер обязан авторизовать каждый вызов, включая повторные попытки.

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

Архитектура PERMIT-7

ШлюзКонтрактПроверка
1. Пользовательподтверждённый actor и tenantсессия или делегированная личность
2. Задачацель и срок действияID задачи и scope
3. Инструментточное имя и версиязапись каталога
4. ОбъектID разрешается серверомвладелец и ACL
5. Эффектread, draft, update или sendотдельное полномочие
6. Согласованиечеловек для значимой записиreceipt для точного действия
7. Результатпроверка в целевой системеreceipt или неизвестный исход

Оркестратор передаёт проверенную личность policy engine. Модель передаёт только предлагаемые аргументы. Доверенный адаптер разрешает ID тикета, проверяет доступ и выполняет действие из allowlist. Токен, tenant и произвольный URL из аргументов модели не принимаются. Итоговая система также проверяет собственные права.

Синтетическая политика

yaml
actor: support-agent-user-42
tenant: example-tenant
purpose: draft_reply
resource: ticket:784
allow:
  - ticket.read
  - reply.draft
expires_at: 2026-09-26T18:00:00Z
requires_human_for:
  - reply.send
  - ticket.update

Манифест иллюстрирует контракт, а не готовую production-конфигурацию. В реальной системе личность приходит из доверенного источника, а ACL проверяется в момент вызова. Запрос модели на reply.send этот манифест не разрешает.

Компоненты и путь исполнения

Каталог публикует узкие схемы инструментов. Валидатор отклоняет неизвестные поля, некорректные ID и списки без лимитов. Политика совместно проверяет actor, tenant, цель, объект и эффект. Сервис согласования связывает решение человека с точным действием и хешем payload. Адаптер применяет короткоживущий делегированный токен либо собственную ограниченную личность и фиксирует результат в целевой системе. Логи хранят идентификаторы и решения, а не чувствительный текст тикета.

ts
// Псевдокод: actor и tenant сервер получает вне модели.
const proposal = toolSchema.parse(modelCall.arguments);
const target = await ticketStore.resolve(proposal.ticketId);
const decision = await policy.evaluate({ actor, tenant, taskId, target, effect: "reply.draft" });
if (!decision.allowed) return deny(decision.reason);
const draft = await adapter.createDraft(target, proposal.body, requestId);
return verifyDraftAtDestination(draft.id, requestId);

Для reply.send нужен отдельный этап согласования получателя и точного текста. Непосредственно перед отправкой права проверяют снова: пока черновик ожидал согласования, доступ мог измениться. Idempotency key и receipt уменьшают риск дубля, но неизвестный результат сначала сверяют в целевой системе.

Ошибки и failure modes

ОшибкаРискМаршрут
Модель указывает тикет другого tenantчтение чужих данныхотказ по ACL на сервере
Alias инструмента ведёт к более широкому действиюнежелательная записьверсия каталога и точный effect
Согласование относится к старому черновикуотправка изменённого текстаhash payload в approval
Роль отозвана после планированияустаревшее разрешениеповторная проверка перед действием
Timeout после отправкидубль при повторесверка receipt в целевой системе
В лог попадает полный текст тикетаутечка данныхредакция и срок хранения

Изоляция процесса не заменяет авторизацию данных. Sandbox ограничивает код, файлы и сеть; политика PERMIT-7 определяет допустимый бизнес-объект и эффект.

Тестирование и production-чек

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

Перед production инвентаризируйте инструменты и эффекты, назначьте владельцев политики, определите пороги согласования, срок хранения аудита, оборот ограниченных учётных данных и процедуру отзыва прав. Отслеживайте отказы, несовпадения approval и неизвестные исходы как сигналы эксплуатации, без выдуманных показателей. Prompt engineering помогает сузить предложения модели, AI-автоматизация — выстроить workflow. Для архитектурного ревью свяжитесь с командой.

CODE_BLOCK.TXT
const target = await store.resolve(proposal.ticketId);
const decision = await policy.evaluate({ actor, tenant, taskId, target, effect });
if (!decision.allowed) return deny(decision.reason);
return verifyAtDestination(await adapter.executeOnce(target, requestId));