Глава 08 · Контроль
Permissions — четырёхслойный гейт между моделью и системой
Модель может попросить tool, но между запросом и исполнением встают четыре независимые проверки: hooks, deny-rules, mode и canUseTool. Любая из них имеет право сказать «нет» — и инструмент не выполнится.
Почему четыре слоя, а не один
В чате с моделью никаких permission нет: всё, что отдаёт API, видит пользователь. В агенте появляется новое право — модель просит harness что-то сделать в реальном мире (записать файл, запустить bash, дёрнуть API). И тут возникает фундаментальный вопрос: кто решает, выполнять ли просьбу. Если один центральный switch — это негибко: или всё запрещаем (тогда агент бесполезен), или всё разрешаем (тогда агент опасен). Поэтому в Claude Agent SDK и в claude-cli решение разложено на четыре слоя, каждый из которых отвечает за свой аспект.
Hooks — это пользовательский код, который вставляется в pipeline и решает свои частные случаи: «не давай выполнять rm -rf вне рабочей директории», «логируй все Bash-вызовы в SIEM», «спрашивай моего тимлида перед deploy». Deny-rules — декларативные политики из settings.json: «инструмент Bash с подкомандой npm publish всегда запрещён». Mode — текущий «режим работы» сессии, который пользователь переключает на лету: planning, autopilot, careful mode. canUseTool — последняя точка, где сам инструмент проверяет специфику (например, Write смотрит, лежит ли путь внутри рабочей директории; Bash матчит подкоманду на patterns). Композиция четырёх слоёв — это и есть «жёсткий контроль без жёсткости»: каждый слой может пропустить, но любой может остановить.
Permission ordering: hooks → deny → mode → allow → canUseTool
Anthropic Agent SDK фиксирует точный порядок: hooks → deny → mode → allow → canUseTool. Это не косметика — это ответ на вопрос «что побеждает, если два слоя несогласны». Hooks бегут первыми, но их allow не закрывает цепочку: deny-rules и ask-rules всё равно проверяются после. Это важная мера: hook не может «подкупить» pipeline, он может только не запретить. Deny-rules — самые приоритетные. Они держатся в любом режиме, включая bypassPermissions. Это сделано буквально: deny — первый шаг внутренней функции hasPermissionsToUseToolInner, и ничто после не может его отменить.
Mode применяется после deny: bypassPermissions автоодобряет всё, что дошло до этого шага; acceptEdits автоодобряет файловые операции; остальные режимы пропускают дальше. Allow-rules — это пред-одобренный список инструментов (allowed_tools). И только если ни один слой не сказал последнего слова, дело доходит до canUseTool — runtime-колбэка, который в интерактивном режиме показывает пользователю UI-prompt, а в headless — спрашивает SDK-консьюмера. В режиме dontAsk этот шаг просто пропускается, и неодобренный заранее tool отклоняется без вопросов. Любая попытка свернуть pipeline к одному «авторитетному» слою — например, считать PreToolUse-хук финальным решением — ломает контракт.
Три типа решения: allow, deny, ask
В типизации claude-cli есть ровно три PermissionBehavior: allow (выполнить, возможно с переписанным updatedInput), deny (отказать с обязательным decisionReason), ask (отправить запрос на подтверждение — UI-prompt в TUI, control-request в SDK, headless-hook в фоновом агенте). Внутри pipeline есть ещё четвёртое значение, passthrough, — но это «маркер: у этого слоя нет мнения». В самом конце passthrough переписывается в ask, потому что когда никто не сказал «можно», по умолчанию надо спросить. Каждое решение несёт типизированный decisionReason (rule, mode, hook, classifier, safetyCheck…) — лог решений становится логом причин, а не строкой «allowed/denied».
Modes: четыре режима, четыре политики по умолчанию
default — стандартное поведение: ничего не автоодобряется, всё неизвестное падает в canUseTool и спрашивает пользователя. Это безопасный дефолт для интерактивной работы. plan — режим планирования: модель может читать файлы и запускать read-only shell-команды, но не писать. Любая попытка Write или Edit превращается в ask. Используется, когда хочется получить «план изменений» без правок. acceptEdits — автоодобрение файловых операций: Write, Edit и базовые shell-команды (mkdir, touch, rm, rmdir, mv, cp, sed) внутри рабочей директории. Bash-команды вне этого списка всё равно спрашивают. bypassPermissions — «yolo»: всё автоодобряется, кроме того, что заблокировано deny-rules, явными ask-rules и hooks. Используется только в изолированных средах, потому что Claude получает полный системный доступ. Mode можно менять прямо в стриме: set_permission_mode / setPermissionMode. Стандартный паттерн — начать в default, посмотреть, что модель собирается делать, и переключиться в acceptEdits, когда подход устраивает.
Rules: декларативный JSON, который нельзя «обойти словами»
Permission rules — это записи в .claude/settings.json формата "ToolName(content)". Конкретный синтаксис: Bash(git status) — точное совпадение; Bash(npm publish:*) — префиксный матч; Edit(~/secrets/*) — paths-pattern; mcp__server__* — все инструменты конкретного MCP-сервера. Три behavior: allow, ask, deny:
{
"permissions": {
"allow": ["Read", "Grep", "Bash(git status)", "Bash(ls:*)"],
"ask": ["Bash(npm publish:*)", "Write(~/.config/*)"],
"deny": ["Bash(rm -rf:*)", "Bash(curl:*)", "Edit(.env)"]
}
}
Главное свойство deny-rules: их нельзя обойти ни режимом, ни хуком, ни SDK-консьюмером. bypassPermissions не снимает их. Hook с allow не отменяет их. Это база безопасности: если в проектных settings лежит "Bash(rm -rf:*)" в deny, ни одна комбинация настроек не даст агенту его выполнить. Allow-rules — наоборот, дают пред-одобрение: "allow": ["Read"] означает, что Read не будет спрашивать пользователя ни в одном режиме (кроме случаев safety-check на чувствительные пути вроде .git/ и .claude/ — те всегда промптят).
Subagent inheritance: child не понизит уровень проверки
Самый недооценённый нюанс, который Anthropic выделяет отдельным warning'ом: subagent наследует bypassPermissions, acceptEdits и auto от родителя без права override. Если родительский агент работает в bypassPermissions, все его subagents тоже автоматически в bypassPermissions — даже если их собственная конфигурация говорит default. Это намеренное решение, и причина важна: subagent — это новая инстанция модели со своим system prompt, у неё может быть меньше осторожности и больше уверенности. Если разрешить ребёнку «понизить» уровень проверки, atomic-trust родительского контекста распадётся. Поэтому child всегда работает не слабее родителя.
Обратное верно: subagent может быть строже родителя. В default-режиме родитель может задать ребёнку permissionMode: 'plan' — и ребёнок будет работать с более жёсткими правилами. Не-override применяется только к трём «sloppy»-режимам (bypass/acceptEdits/auto), где ребёнок мог бы расширить полномочия. Любой runtime, который exposes bypassPermissions пользовательскому коду, обязан иметь явный deny-список, потому что allow-rules при bypass не работают: всё, что не в deny, разрешено.
Hooks как extension point
Hooks — это «крючки», в которые пользователь вставляет произвольный код, чтобы участвовать в pipeline. С точки зрения permissions релевантны три события: PreToolUse (бежит до решения, может вернуть allow или deny), PermissionRequest (бежит, когда pipeline остановился на ask, особенно важен в headless-режиме без UI), PermissionDenied (бежит после deny — для логирования и наблюдаемости). Хуки — это связь с главой «Hooks»: там разобрано, как они оформляются, как matcher'ы работают, как hook может приостановить tool или модифицировать его аргументы. Здесь важна позиция: hook — первый слой pipeline, но не последний. Это значит, что hook не может «открыть дверь», но всегда может её захлопнуть.
Check `deny` rules. If a deny rule matches, the tool is blocked, even in `bypassPermissions` mode.
— Anthropic · «Configure permissions» (Agent SDK docs)Источники главы
- [ant] Configure permissions — Agent SDK Docs (порядок hooks → deny → mode → allow → canUseTool; режимы; subagent inheritance warning)
- [cli] Конспект: Permission & Policy (четырёхслойный гейт,
hasPermissionsToUseToolInner,passthrough, safety-check immunity) - [cli] Конспект: Anthropic API distill (раздел «Safety / policy / permissions guidance» — каноничный порядок ordering и C-07 / C-08 invariants)