RBAC для AI-агентов в Laravel: три слоя, которые LLM не обходит

Промпт, проверка в PHP-хендлере, фильтр в SQL — три уровня, и только два нижних держат границу. Для соло-бэкенда с агентом в очереди это значит явно передавать пользователя в каждый вызов инструмента, а не полагаться на системный промпт — разбор в материале.
Почему агенты не попадают в ваш web middleware
AI-агенты не открывают браузер, не жмут кнопки и не проходят через стандартный HTTP-поток с сессией и middleware. Они ходят в API, вызывают function calling, крутятся в background queues и иногда в CLI.
Отсюда типичная утечка: инструмент get_open_tickets делает Ticket::where('status', 'open')->get() без scope — и агент видит все открытые тикеты, хотя в контроллере тот же запрос ограничен auth()->id(). Web-RBAC отработал бы на человека; агент получил god-mode, потому что путь другой.
Агентский runtime — Prism, Echo или свой agent loop — по сути внешний оркестратор без HTTP-сессии. Те же риски detached context, что у связки Cursor и MCP: инструменты есть, а «кто сейчас пользователь» в контексте не зафиксирован.
LLM — прокси, не субъект прав
В материале не вводится отдельная сущность «агент-пользователь» в RBAC. Модель — LLM как прокси человека (proxy for the user). Права наследуются от acting user — того, кто запустил агента.
Механизм передачи идентичности по цепочке:
- DTO
AgentContextс полемactingUserинжектится в каждый tool handler; - в queue job
RunAgentLoopвызываетсяAuth::setUser($actingUser), чтобы Gates и Policies видели того же пользователя, что и веб-контроллер; - audit пишет
causer_idчеловека плюсagent_session_id.
Worker под generic system user либо блокирует всё, либо обходит ограничения — отдельный service account для агента не предлагается.
// Упрощённая идея из статьи: acting user в контексте job
Auth::setUser($agentContext->actingUser);
Gate::authorize('close', $ticket);
Серверный периметр: Gates, scopes и динамический registry
Безопасность сидит в application-level и database-level, не в промпте.
| Слой | Что делает в статье |
|---|---|
| Gates / Policies | Gate::authorize() до мутации, например close на тикете |
| Eloquent scopes | accessibleBy, Global Scopes — фильтр на SQL, не «попроси модель отфильтровать» |
AgentToolRegistry |
Список tools собирается под permission set пользователя |
laravel-permission-manager |
ABAC, иерархии ролей, multi-tenancy, allow/deny overrides |
Пример ABAC: Regional Manager может одобрить refund до $5 000 только в своём регионе; у Director — отдельное правило про conflict of interest на direct reports.
Отдельного Laravel middleware «для агентов» в посте не описано — акцент на application-level (Gates) и database-level (scopes). Spatie Permission упоминается как возможная основа для scope accessibleBy.
При AuthorizationException orchestration возвращает error string в LLM, и пользователь видит что-то вроде «обратитесь к старшему коллеге» — эскалация через агента, а не тихий отказ.
Почему prompt-level security не работает
Фраза «Only show John his own data» в system prompt — это доверие вероятностному текстовому генератору границу безопасности. Prompt-Level в архитектурной таблице помечен как «Never for security» — годится для тона и подсказок UI, не для доступа к данным.
Провальный сценарий: system prompt объявляет ассистента «read-only analytics», но в registry лежит update_customer_status. LLM всё равно вызовет mutation tool.
Жёсткий периметр по тексту:
Gate::authorize()внутри tool;- SQL scopes и metadata-фильтры в vector DB;
- write-tools не попадают в registry без
can('update', …)/can('delete', …).
Production: audit, workers и vector search
Для non-repudiation предлагается ActivityLog: causer_id (human), agent_id / agentSessionId, tool_name, parameters, llm_reasoning — с redact PII в параметрах.
В Horizon и multi-thread workers риск «user bleeding» при Auth::setUser: нужен reset auth или scoped context между job'ами.
Для Qdrant и Pinecone — синхрон metadata (например department_id) и фильтр при query. Иначе confidential docs попадают в context window, даже если SQL в другом tool был чистым.
По бюджету токенов: не отдавать LLM полный dataset «отфильтруй сам» — filter at SQL; кеш resolved permission tree на старт agent loop (ReAct multi-step).
Два подхода к registry: не сглаживать противоречие
В статье два тезиса из разных секций — оба валидны, но для разных целей.
Секция 3 — удобство отладки: не скрывай tool от LLM, пусть Gate блокирует вызов. Агент видит полный каталог, ошибка авторизации объяснима.
Секция 6 — жёсткий периметр и экономия токенов: не регистрируй write-tools без прав. Меньше шума в system prompt, точнее tool selection.
Для соло-разработчика с растущим числом agent tools разумная схема: mutation tools — только при явном can, read-tools — шире, но всё равно с Gate внутри handler'а. Определения инструментов здесь — аналог skills/tools в agent loop; граница безопасности в PHP, не в rules IDE.
Что вынести в свой стек
Архитектурная таблица сравнивает четыре слоя: Prompt-Level, Application-Level (Gates/Policies), Database-Level (Global Scopes / Postgres RLS), API Proxy/Gateway. Для Laravel monolith рекомендация — Application-Level + permission-manager; для multi-tenant SaaS с тяжёлыми vector queries — Database-Level / vector metadata filters.
Практический чеклист после чтения:
- В каждом tool handler есть acting user из
AgentContext, не system worker. - Мутации проходят
Gate::authorize()до изменения данных. - Выборки ограничены scopes, не инструкцией модели.
- Registry и audit согласованы: кто вызвал tool, с какими параметрами, от имени кого.
Конкретные версии Laravel и PHP в исходном посте не указаны; latency-бенчмарки permission-manager тоже не приведены — только качественные предупреждения.
Источники
- @hosseinhezami — Giving AI Agents the Same RBAC Rules as Your Users: Building a Laravel Permission Layer LLMs Actually Respect (Dev.to, опубликовано 6 сентября 2026; доступ 6 сентября 2026)