Если permission mode задаёт общий ритм, то permission rules уточняют отдельные инструменты, и именно они превращают доверие из грубого переключателя в точную политику. Правила имеют форму Tool или Tool(specifier) и живут в трёх массивах: allow, ask и deny. Порядок их разрешения строгий и его надо знать: совпавший deny имеет приоритет, затем учитывается ask, и только потом allow. Более широкое разрешение не отменяет более строгое правило из другого scope.
Это правило приоритета - основа безопасности всей схемы. Пользователь не может снять репозиторный или managed запрет, просто дописав широкий allow: deny из любого scope переживёт любое разрешение. Rules из разных scopes объединяются, а не заменяют друг друга, и для deny это особенно важно - обязательный запрет на чтение секретов или на rm -rf нельзя обойти локальной настройкой. Политика складывается из всех слоёв, а самый строгий выигрывает.
Для Bash решающее значение имеет граница команды. Трейлинг-wildcard задают суффиксом : или равнозначной формой через пробел: Bash(npm run test:) и Bash(npm run test ) совпадают одинаково и обе держат границу слова - правило покроет эту команду с аргументами, но не произвольную, чьё имя лишь начинается так же (в отличие от Bash(ls) без границы, который поймает и lsof). Claude Code разбирает составные shell-команды, а сопоставлять можно и входные параметры через Tool(param:value), а не только текст команды.
Полезно один раз увидеть правила разной ширины рядом, чтобы чувствовать разницу между точным и чрезмерным. Ниже - от одной конкретной команды до всего Bash, который почти всегда избыточен. К этой шкале возвращаются, составляя allowlist: цель - разрешить именно нужное семейство команд (build, lint, test), а не открыть весь инструмент, потому что "так проще". Ширина правила - это ширина доверия.
У путей в Read и Edit своя семантика, и её легко спутать. Двойной слэш означает абсолютный путь от корня файловой системы, тильда - относительно home, точка-слэш или путь без префикса - относительно текущего каталога, а отсутствие specifier означает весь инструмент. Отдельно стоит одиночный слэш: он отсчитывается не от корня проекта, а от источника самих настроек. В project-настройках /src это действительно корень репозитория, но в user-настройках Read(/secrets/**) закроет ~/.claude/secrets, а вовсе не папку secrets в проекте; local-настройки и правила из CLI отсчитывают от каталога запуска. Чтобы одно правило работало во всех проектах, нужен двойной слэш или тильда. Важнейшая тонкость: это не та нотация, что в sandbox.filesystem, где /tmp - обычный абсолютный путь. Смешение двух синтаксисов - частая причина политики, которая выглядит строгой, но не совпадает.
Полезно один раз увидеть цельный блок permissions с allow, ask, deny и additionalDirectories, чтобы держать перед глазами рабочую форму. Ниже - пример: чтение и правка в src, тестовые команды и один домен разрешены; push и выход из sandbox спрашиваются; чтение .env и секретов и rm -rf запрещены. К этой форме возвращаются, настраивая политику проекта: она читается как явное описание того, что агенту можно, что с подтверждением и что нельзя.
Место правил определяет их роль. /permissions показывает и меняет разрешения в интерактивной сессии, в том числе источник каждого активного правила. Для командной, обозримой политики правила кладут в .claude/settings.json; личные исключения - в settings.local.json или user-настройки; обязательные ограничения - в managed. И project-конфигурация активируется только после доверия к workspace: до одобрения незнакомый checkout не должен получать свои hooks, MCP или policy.
Инженерный вывод - оптимизировать allowlist под именованные задачи, а не под удобство. Разрешают build, lint, unit-тесты, typecheck; а deployment, публикацию пакетов, apply инфраструктуры, доступ к credentials и разрушительные миграции оставляют в ask или deny. Типичные провалы - широкий Bash(*) вместо семейства команд, смешение синтаксиса путей permission и sandbox, попытка обойти чужой deny своим allow. Составляйте правила под конкретные задачи, а самый строгий слой держите неснимаемым.
Bash(npm run lint) # только точная команда
Bash(npm run lint:*) # команда и последующие аргументы
Bash(git diff:*) # семейство безопасного чтения diff
Bash(*) # весь Bash - почти всегда чрезмерно
# Одиночный слэш - от источника настроек:
# project settings -> <корень репозитория>/path
# user settings -> ~/.claude/path
# local / CLI -> <каталог запуска>/path
# Абсолютный путь - только двойной слэш: Read(//Users/alice/**){
"permissions": {
"allow": [
"Read(src/**)", "Edit(src/**)",
"Bash(npm run test:*)",
"WebFetch(domain:docs.example.com)"
],
"ask": ["Bash(git push:*)", "Bash(dangerouslyDisableSandbox:true)"],
"deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(rm -rf:*)"],
"additionalDirectories": ["../shared-types"]
}
}
// deny > ask > allow; deny из любого scope не снять широким allow