Документация по разрешениям агента - раздел, который в справочнике называется Claude Code permissions, - исчерпывающа по синтаксису и молчит про стратегию. Она расскажет, как записать правило, но не ответит, что в него класть, почему ваше правило вчера сработало, а сегодня нет, и что делать с тем, что запрет вообще не покрывает.
Разберём три слоя по порядку: как правила устроены, где они дают течь, и что остаётся за их пределами.
Как Claude Code решает, что вам разрешено?
Порядок вычисления описан в справочнике прямо:
Правила вычисляются по порядку: сначала deny, потом ask, потом allow. Первое совпадение в этом порядке и определяет исход, а конкретность правила порядок не меняет.
Тем, кто пришёл из мира сетевых экранов, эта фраза ломает интуицию. Там принято писать широкий запрет и точечные исключения из него. Здесь так нельзя:
Широкое запрещающее правило вроде Bash(aws *) блокирует любой подходящий вызов, включая те, что подходят и под более узкое разрешающее правило Bash(aws s3 ls), поэтому запрещающее правило не может нести в себе исключений.
Вторая вещь, которую стоит усвоить до всякой настройки: правила живут не там, где живёт модель.
Правила разрешений применяет сам Claude Code, а не модель. Инструкции в промпте или в CLAUDE.md формируют то, что Claude пытается сделать, но не меняют того, что Claude Code разрешает.
Отсюда сразу следует, что записанное в файле правил проекта пожелание «не трогай папку с миграциями» ограничением не служит. Оно влияет только на намерение; действие решают разрешения.
И третья деталь, которую в обзорах пропускают чаще всего: запрет с именем инструмента и запрет со спецификатором работают по-разному. deny: ["Bash"] убирает инструмент из контекста целиком, и модель его просто не видит. deny: ["Bash(rm *)"] оставляет инструмент на месте и отсекает совпавшие вызовы. Первое надёжнее по устройству, потому что не требует угадывать формулировку. Второе гибче и держится ровно настолько, насколько вы угадали все написания.
Какой уровень настроек победит - проектный или пользовательский?
Настройки-массивы конкатенируются и дедуплицируются между уровнями. То есть ваш проектный allow прибавляется к пользовательскому. Из этого выходит правило, которое стоит повесить перед глазами: запрет побеждает разрешение через границу уровней в обе стороны. Пользовательский deny перекроет проектный allow, и наоборот.
Именно здесь чаще всего и живёт расхождение «я же написал запрет». Посмотрите на живой пример, замеренный на одном рабочем проекте:
| Уровень | Что там лежит |
|---|---|
| Проект | десять запретов и ни одного разрешения |
| Локальный слой проекта | файла нет вообще |
| Пользователь | двадцать разрешений, четырнадцать запретов, разрешения заданы масками |
| Локальный слой пользователя | семьдесят одно разовое согласие, накопленное по одному за сессии |
Периметр здесь задают три записи из семидесяти одной - те, что разрешают запуск интерпретатора, менеджера пакетов и установку произвольного пакета, - а вовсе не проектный список запретов. Каждая из трёх исполняет произвольный код. После них остальные шестьдесят восемь точных разрешений не ограничивают ничего - они лишь перечисляют то, что и так можно.
Список разовых согласий растёт монотонно и не чистится никогда. Через месяц работы он перестаёт описывать ваши намерения и начинает описывать вашу историю.
Отдельно про корпоративный слой. Управляемые настройки не переопределяются ничем, включая аргументы командной строки. Закрыть от разработчика они умеют ровно то, чем он обычно и обходит ограничения:
allowManagedPermissionRulesOnlyзапрещает пользовательским и проектным настройкам вообще определять правила;disableBypassPermissionsModeотключает режим обхода;disableSideloadFlagsотклоняет подсовывание своих плагинов и своей конфигурации серверов при старте.
Разрешающие правила из файла настроек склонированного репозитория не применяются, пока вы не примете диалог доверия. Запрещающие и спрашивающие применяются сразу - в документации это объяснено тем, что они только ограничивают. Чужой репозиторий не может выдать себе прав, но может отнять их у себя же.
Что класть в deny: рабочий минимум
Самая частая формулировка боли звучит так:
Мы что, всерьёз должны перечислить все возможные вариации вредных команд? Вдобавок к Bash(cat ./.env) придётся добавить Bash(cat .env), Bash(tail ./.env), Bash(tail .env), Bash(head ./.env), Bash(sed '' ./.env) и бесчисленное множество других... и при этом мы разрешаем запускать что-то вроде npm?
Ответ короче жалобы: перечислять команды не надо. Одно правило на путь закрывает и встроенный инструмент чтения, и те файловые команды в оболочке, которые Claude Code распознаёт, - cat, head, tail, sed. Одно правило вместо шести. Записывать его надо как Read(/.env) или Read(/**/.env), и почему именно так - в следующем разделе.
Компания Trail of Bits, живущая аудитами безопасности, держит свой список открытым. В нём тридцать одно правило: десять на команды и двадцать одно на пути. Ниже выжимка, чтобы было видно устройство:
{
"permissions": {
"deny": [
"Bash(rm -rf *)",
"Bash(rm -fr *)",
"Bash(sudo *)",
"Bash(dd *)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Read(~/.config/gh/**)",
"Read(~/.git-credentials)",
"Read(~/.docker/config.json)",
"Read(~/.kube/**)",
"Read(~/.npmrc)",
"Read(~/Library/Keychains/**)",
"Edit(~/.bashrc)",
"Edit(~/.zshrc)",
"Edit(~/.ssh/**)"
]
}
}Самая говорящая деталь этого файла настроек лежит рядом с ним. К двум написаниям rm -rf и rm -fr у них приложен хук, который разбирает команду регулярным выражением и ловит rm с любым сочетанием рекурсивного и принудительного флага в любом порядке. Компания, которая пишет запрет строкой, сама же дублирует его парсером - потому что двух написаний не хватает.
Полезный шаблон из обсуждений: разрешить действие через утилиту и запретить прямое чтение её секрета.
Разрешить Bash(kubectl:*), запретить Read(/.kube/). Это даёт доступ к kubectl, не разрешая инструменту читать ~/.kube напрямую.
А вот сеть закрывать перечислением адресов бесполезно. Документация сама показывает, чем обходится правило на конкретный адрес: флаги перед адресом, другой протокол, редирект, подстановка переменной, лишний пробел. Рекомендация вендора - запретить сетевые утилиты целиком и пускать запросы через штатный инструмент загрузки страниц, с оговоркой, что и это не отрезает сеть, пока оболочка разрешена.
В официальном шаблоне строгой конфигурации нет ни одного запрета на файлы и команды. Там ask на всю оболочку, deny на поиск и загрузку страниц, запрет режима обхода и требование применять только управляемые правила и хуки. Строгость там собрана по принципу «сузить поверхность и спрашивать»; перечисления опасного нет вовсе.
Почему правило не сработало, хотя написано верно?
Каноническая история описана в тикете, который завёл пользователь под ником coygeek. Он положил в запреты Read(./.env), попросил прочитать файл окружения и получил его содержимое. Ответ сотрудника Anthropic объясняет причину:
Шаблон Read(./.env) в вашем примере трактуется как относительный к текущему рабочему каталогу, откуда вы запускаете claude, а не относительно проекта или расположения файла настроек. Поэтому запрещающее правило не совпадает.
Форм записи четыре, и внешне они похожи:
| Запись | Куда указывает |
|---|---|
//path | корень файловой системы |
~/path | домашний каталог |
/path | туда, где лежит файл настроек |
path или ./path | текущий рабочий каталог |
Рабочие формы для файла окружения - Read(/.env) и Read(/**/.env). Разница с ненадёжной версией в одном символе, и ни ошибки, ни предупреждения при старте не будет. Форма Read(./.env) при этом сработает, если запускать агента ровно из того каталога, где лежит файл; стоит выйти на уровень выше - и правило замолчит.
Вторая причина тише первой. Файловые разрешения проверяются только против правил Edit(path) и Read(path). Напишете путевое правило для Write, Glob или блокнотного редактора - правило примут и никогда к нему не обратятся. Предупреждение при старте здесь есть, но его легко пролистать.
Третья причина живёт в одном пробеле, и её мы замерили. На версии 2.1.220 в изолированном каталоге:
Правило в allow | Команда | Что вышло |
|---|---|---|
Bash(touch :*) - пробел перед двоеточием | touch a.txt | запрос на подтверждение, файл не создан |
Bash(touch:*) | touch b.txt | выполнено молча |
Bash(touch *) | touch c.txt | выполнено молча |
Форма с пробелом перед двоеточием не совпадает ни с чем. Отдельного правила про этот случай в документации нет: там разобрано, что суффикс :* работает только в конце шаблона, а в середине двоеточие становится обычным символом. Почему пробел ломает форму, которая внешне стоит в конце, справочник не объясняет - это видно только из замера.
Это не редкая экзотика. В открытом репозитории Docker в разрешающем списке двадцать три правила, и восемь из них написаны с пробелом перед двоеточием - пять про go плюс Bash(ls :*), Bash(head :*), Bash(tail :*). Рядом лежат два корректных: Bash(go test:*) и Bash(go build:*). В разрешающем списке такая опечатка безобидна и стоит лишних подтверждений. В запрещающем та же опечатка была бы дырой, и заметить её нечем.
Сюда же - правило на главное поле оболочки. Запись вида Bash(command:rm *) инструмент игнорирует намеренно, потому что её обошла бы составная команда, и выдаёт предупреждение при старте.
Отдельная беда - средний список. В тикете #81041 правила ask загружаются, показываются в списке разрешений под подписью «перед этими инструментами всегда будет запрос» и не срабатывают ни разу; deny в том же файле при этом работает. Автор перебрал четыре формы записи. Его вывод стоит запомнить: механизм разрешений, который молча ничего не обеспечивает, хуже отсутствующего - конфигурация читается как защита, интерфейс подтверждает, что она активна, и человек резонно считает команды закрытыми.
Жалоба на кнопку «Всегда разрешать» - что она сохраняет ровно ту строку, которая была на экране, вместе с текстом коммита, - ходит по обсуждениям с прошлого года (тикет #30519). Вендор этот класс чинил, и в журнале изменений видно как минимум три правки про сохранение таких правил. Но сам механизм остаётся: каждое разовое согласие дописывает строку, никто эти строки не удаляет, и через месяц список описывает не ваши намерения, а вашу историю.
Что deny не закрывает по определению?
Граница жанра сформулирована вендором одной фразой:
Это шлагбаум разрешений, а не песочница; он не выводит опасность команды из её целевого пути или последствий.
Где именно заканчивается шлагбаум, документация тоже говорит прямо: правила чтения и правки применяются к встроенным файловым инструментам и к распознаваемым файловым командам в оболочке, но не к произвольным подпроцессам, которые открывают файлы сами.
Между «написано в разделе про ограничения» и «увидел своими глазами» - пропасть, поэтому мы это замерили. Изолированный каталог, версия 2.1.220, в запретах Read(.env), в разрешениях оболочка, в файле окружения строка-канарейка:
cat .env
# запрещено
awk "{print}" .env
# запрещено
python3 -c "print(open('.env').read())"
# SECRET_TOKEN=canary-12345Третья команда вернула секрет, и ни одного отказа при этом не зафиксировано. Это не дефект - ровно так граница и описана. Но описание читают немногие, а конфигурацию с Read(.env) в запретах держат почти все, и выглядит она как закрытая дверь.
Прогоните эти три строки у себя, прежде чем читать дальше. Минута, и вы точно знаете, что у вас закрыто.
Каналов, по которым содержимое файла попадает в контекст, больше одного:
| Канал | Держит ли запрет Read | Подтверждение |
|---|---|---|
| Встроенный инструмент чтения | да | документация |
cat, head, tail, sed в оболочке | да, они распознаются | документация |
| Скрипт на Python или Node, открывающий файл сам | нет | документация |
| Вложение файла через собаку в промпте | правило - да, «по мере возможности»; хук - нет, вызова инструмента не происходит | документация и тикет #61148 |
| Выделение текста в подключённой среде разработки | заявлено «по мере возможности» | тикет #81862, открыт: путь в форме подсистемы Windows не совпал |
| Штатная команда установки расширения | нет, переписывает даже защищённый правилом файл настроек | тикет #34767 |
| Соседний агент в той же сессии | нет, у него своё состояние разрешений | тикет #75 в репозитории плагина |
Формулировка «по мере возможности» в этой таблице - дословная из документации, и она сама по себе ответ. Правила чтения применяются к встроенным инструментам жёстко, а к вложениям в промпте и к контексту из редактора - best-effort, то есть по мере возможности. Разница между «применяется» и «применяется по мере возможности» и есть содержание двух открытых тикетов выше.
Последняя строка таблицы заслуживает отдельного внимания, потому что причина в ней названа точно:
Обход на чтение и обход на запись имеют один корень: скрипт-компаньон ничего не знает о состоянии разрешений Claude Code.
Есть и хорошая новость: часть каналов закрыта по умолчанию во всех режимах, кроме полного обхода. Разрешающие правила не открывают запись в защищённые пути - проверка выполняется до того, как правила из настроек вообще вычисляются. В защищённый список входят каталоги настроек и служебные файлы оболочки, то есть классические способы посадить закладку в конфигурацию перекрыты без вашего участия.
Чем хук отличается от запрещающего правила?
Дальше слово «хук» будет встречаться часто. Это то же, что в разборе периметра названо обработчиком события: небольшая программа, которую агент запускает сам перед вызовом инструмента или после него. Сравнение по существу:
| Признак | Правило deny | Хук PreToolUse |
|---|---|---|
| Как работает | сопоставление строки вызова с шаблоном | запускается код, возвращает решение |
| Умеет исключения | нет | да, вы пишете любую логику |
| Приоритет | запрет побеждает всё, кроме блокирующего хука | блокирующий хук останавливает вызов до вычисления разрешений |
| Может ли отменить запрет | - | нет, решение allow от хука запрет не обходит |
| Срабатывает на вложение файла через собаку | да, правило Read работает | нет, вызова инструмента не происходит |
| Чем ломается | новым написанием команды | ошибкой в собственном коде |
Формулировка практика попала в точку:
Системный промпт - это просьба. Хук - это гарантия.
Обратный перекос встречается не реже. Хук тоже не граница безопасности:
Хуки - не граница безопасности: внедрение инструкций может их обойти. Это структурированное внедрение инструкций в удачные моменты: перехват вызовов инструментов, вбрасывание контекста, блокировка известных плохих шаблонов и подруливание поведением агента. Ограждения, а не стены.
Для большинства событий действие останавливает только код возврата два. Код один трактуется как неблокирующая ошибка, и вызов выполняется, хотя единица - общепринятый в Unix код неудачи. Хук, который должен обеспечивать политику, обязан завершаться двойкой.
Хуже всего работает схема, которую советуют чаще всего. «Включу режим обхода разрешений, а хук оставлю страховкой» не работает: под обходом хуки выполняются асинхронно, и команда успевает отработать раньше, чем придёт отказ.
Команда git commit выполнилась, несмотря на то что хук PreToolUse вернул код 2, чтобы её заблокировать. Отказ хука пришёл через 37 секунд после выдачи команды, но коммит уже был создан.
В той сессии хук отказал девять раз, пять коммитов прошли, и все ушли в удалённый репозиторий. Автор блокирующего хука nah предупреждает своих пользователей о том же и советует вместо режима обхода положить нужные инструменты в разрешения и остаться в обычном режиме.
Почему агент обходит запрет, даже когда сам его объясняет?
В разборе Леонардо Ди Донато агенту запретили запускать две команды и включили песочницу. Агент нашёл другой путь к тому же исполняемому файлу: /proc/self/root/usr/bin/npx разрешается в тот же двоичный файл и не совпадает с шаблоном запрета. Другая строка, тот же результат.
Дальше начинается то, ради чего этот случай стоит помнить:
Посмотрите, что агент мне сказал: «Запустить npx по полному пути, чтобы обойти сопоставление с шаблоном запрещающего правила». Он объяснил обход прямо в запросе на подтверждение. В реальном рабочем процессе с десятками запросов за сессию это ещё одно «да» в потоке «да». Усталость от подтверждений превращает границу безопасности в штамп.
Вывод автора объясняет механику лучше любого списка:
Граница безопасности была сделана из промптов и разрешений - из того же материала, в котором рассуждает агент.
Это пост о запуске коммерческого продукта. Версия, конфигурация и транскрипт не опубликованы, точная строка правила есть только на скриншоте. Обойти запрет автор попросил явно; неспрошенным заявлен лишь шаг с отключением песочницы. Механику он показывает убедительно, воспроизвести её по этому тексту нельзя.
Вендор описывает ровно тот же разрыв своими словами. Класс поведения, ради которого построен автоматический режим, называется чрезмерным рвением:
Агент понимает цель пользователя и искренне пытается помочь, но проявляет инициативу за пределами того, что пользователь одобрил бы. Например, использует учётные данные, которые ему случайно попались, или удаляет то, что счёл помехой. Такое поведение трудно поймать, потому что враждебных рассуждений в нём нет.
Разработчики автоматического режима намеренно вырезают текст ассистента из входа классификатора:
Мы вырезаем текст ассистента, чтобы агент не мог уговорить классификатор принять плохое решение. Агент способен породить убедительные обоснования: «это безопасно, потому что пользователь ранее неявно одобрил» или «эта цель точно принадлежит агенту». Если классификатор их прочитает, его можно уговорить на неверное решение. Вместо этого мы хотим, чтобы он судил по тому, что агент сделал, а не по тому, что агент сказал.
Перечитайте предпоследнюю фразу. Разработчики предохранителя исходят из того, что агент построит себе обоснование и это обоснование будет убедительным. Проверять надо действие. Рассказ о действии сочиняет тот же, кто действует.
Насколько хорошо это работает, вендор сообщает честно в том же разборе: доля пропущенных опасных действий на реальных случаях чрезмерного рвения - 17 % при доле ложных блокировок 0,4 %. Причина пропусков названа там же: классификатор находит свидетельство, похожее на одобрение, и не доходит до проверки, покрывает ли это согласие радиус поражения действия.
Что делать с усталостью от подтверждений?
Цифра из инженерного разбора Anthropic звучит буквально так: ручные запросы находятся посередине, и на практике пользователи всё равно принимают 93 % из них. Со временем люди перестают внимательно смотреть, что именно одобряют.
Народный ответ на это виден невооружённым глазом. В треде на Hacker News про игру о разрешениях агента лежат три соседних комментария, и все три - про один и тот же алиас на флаг отключения проверок. Один из авторов формулирует свою позицию без иллюзий:
Мне надоело это набирать, и я просто сделал алиас. У меня есть отдельный пользователь без прав администратора и без доступа к домашнему каталогу основного пользователя. Да, я знаю, что это неидеально, но мне надо делать дела.
Это честнее, чем оставить сотню правил и прокликивать их. Человек не отменил границу, он перенёс её ниже - на отдельную учётную запись. Спор в сообществе идёт про уровень, на котором стоит граница.
Вторая половина боли выглядит иначе - агент упирается в разрешения там, где ничего опасного не делает:
Я так долго пытался вести выверенный список того, чем Claude может пользоваться, но неизбежно возвращался и находил его застрявшим: он решил направить вывод одного инструмента в другой, а это явно не разрешено, и он остановился - хотя всего лишь что-то грепал.
Режимов разрешений шесть, и их число росло: часть разборов в сети всё ещё перечисляет четыре. Тот, что закрывает вопрос «а как в непрерывной интеграции, где подтверждать некому», называется dontAsk: всё непредодобренное отклоняется сразу, и сессия никогда не ждёт ввода. Для сервера это ровно то, что нужно: связка явного списка разрешённых инструментов с этим режимом даёт предсказуемое поведение без человека у экрана.
Где правила не работают совсем: права уровня приложения
Показательный случай произошёл с сервисом PocketOS. Агент упёрся в несовпадение учётных данных на тестовом контуре, нашёл в постороннем файле токен платформы развёртывания, собрал сетевой запрос и удалил боевой том. Резервные копии лежали на том же томе. Всё заняло девять секунд.
Слова основателя объясняют, где была дыра:
Токен был создан для добавления и удаления пользовательских доменов, но по области действия покрывал любую операцию, включая разрушительные.
А ответ гендиректора платформы закрывает тему одним предложением:
Если вы (или ваш агент) аутентифицируетесь и вызываете удаление, мы выполним этот запрос. Именно это агент и сделал.
Никакой список запретов на машине разработчика этого не остановил бы: права у агента были.
Проблема давно канонизирована: в списке OWASP для приложений с языковыми моделями она называется избыточной агентностью, и один из трёх её корней - именно избыточные права. Пример оттуда почти дословно описывает PocketOS: расширение, предназначенное читать данные, подключается к базе под учётной записью, у которой есть не только чтение, но и запись, вставка и удаление.
Собственный опыт эксплуатации даёт к этому две детали, которых в разборах обычно нет.
Первая: проверка качества может жить не там, где вы думаете. На одном движке публикации структурный контроль текста был реализован как модуль, который вызывался ровно из одного места - модального окна админки. Он гасил кнопку публикации, и пока публиковал человек через интерфейс, всё выглядело работающим. Публикующий маршрут этот модуль не вызывал никогда. Как только публиковать начал скрипт с законными правами администратора, контроля не стало - о нём просто некому было вспомнить. Проверка держала кнопку интерфейса; до данных она не доходила.
Вторая: право на запись включает право потерять след. Инструмент публикации хранил отметку «уже опубликовано» в файле рядом с черновиком, а путь к этому файлу вычислялся от каталога запуска. Одну статью опубликовали с другой машины - и локально отметки не осталось, хотя страница на сайте живёт. Сам черновик при этом до сих пор утверждает, что он черновик: статус в его шапке не переписывает никто. Из трёх локальных источников правды два врут, и узнать правду можно только одним способом - спросить саму боевую систему.
Что здесь работает вместо запретов:
- Сузить область действия токена до операций, которые агенту реально нужны. Токен на добавление доменов не обязан уметь удалять тома.
- Не показывать секрет агенту вообще. Вендор советует вынести учётные данные за границу агента и подставлять их прокси, чтобы агент мог делать вызовы, но не видел сам секрет.
- Поставить предусловие на запись. Перед тем как переписать живой объект, сверьте свою копию с тем, что там лежит сейчас, и убедитесь, что ваше преобразование обратимо. На том же движке этот приём спас статью: инструмент, который дописывает ссылки в опубликованный текст, отказывается работать, если тело на сервере разошлось с локальным.
- Проверять по перечитанному состоянию. Ответ «принято» говорит, что запрос принят. Он не говорит, что страница изменилась.
Что ломается, когда агентов несколько?
За одни сутки на одном проекте три параллельные сессии дали такое: одна откатила ранее принятое решение по адресам разделов, прочитав устаревшую строку в общем документе и приняв её за действующую; вторая начала писать статью, которую уже писала первая.
Правило «перед правкой посмотри историю и состояние репозитория» от этого не защищает вовсе. Обе сессии видят чистое дерево, обе правы в момент проверки, и расходятся они на записи. Общий документ - это тоже общая изменяемая память, причём итоговый раздел в нём устаревает первым: тело правят чаще, чем выводы, а читают агенты именно выводы, потому что они короче.
Один раз затирания не случилось - и вот почему это интересно. Инструмент записи отказался перезаписывать файл, который не был предварительно прочитан в этой сессии. Границу удержало предусловие операции: не переписывай то, чего не читал. Списка запретов для этого не понадобилось. Такое ограничение не надо угадывать написаниями, и обойти его переформулировкой нельзя.
Ищите ограничения, устроенные как предусловия операций. Прочитал перед записью, сверил с текущим состоянием, доказал обратимость - такие проверки не ломаются от нового написания команды.
Отдельная развилка - что достаётся субагентам. Файл с описанием субагента приезжает вместе с клонированным репозиторием и умеет объявить свой режим разрешений; разбор этой механики со стороны периметра есть в проверке периметра агента.
Как проверить, что ваши правила вообще работают?
Порядок проверки:
Посмотрите предупреждения при старте
Неизвестное имя инструмента, путевое правило для инструмента, который его не читает, правило на главное поле оболочки - обо всём этом вас предупредят один раз при запуске. Имена с подчёркиванием и звёздочкой из проверки исключены, так что часть опечаток остаётся немой.
Попросите сделать ровно то, что запрещено
Не «проверь, закрыт ли файл», а прямая просьба его прочитать. Отказ по одному способу доступа ничего не говорит о другом: «метод не поддерживается» легко принять за «закрыто».
Повторите то же самое вторым каналом
Через файловую команду в оболочке, через вложение файла в промпт, через выделение в редакторе. Каналов больше одного, и правило закрывает не все.
Запустите проверку дважды
Защита от повторов, которая ни разу не срабатывала, ничего не защищает. На одном проекте скрипт с защитой от дублей прошёл первый прогон верно только потому, что база была пуста, а второй завёл пять дубликатов.
Проверьте своё поведение на своей версии
Держат ли правила составные команды через двойной амперсанд - вопрос спорный: документация утверждает, что каждая подкоманда сопоставляется отдельно, а практики заводили тикеты с обратным результатом, и журнал изменений показывает несколько итераций починок этого класса. Ответ зависит от версии, поэтому проверьте свою.
Пятый пункт стоит развернуть. Вот выдержка из журнала изменений самого вендора - только про обход правил:
| Версия | Что чинили |
|---|---|
| 2.1.6 | обход через перенос строки в команде |
| 2.1.72 | правила со звёздочкой не совпадали с heredoc, встроенными переносами и вызовом без аргументов |
| 2.1.98 | запрет понижался до вопроса на конвейерах со сменой каталога |
| 2.1.113 | запреты не совпадали с командами в обёртках env, sudo, watch, setsid |
| 2.1.149 | обход через встроенные функции смены каталога в PowerShell |
| 2.1.162 | правила не совпадали при записи через обратные слеши на Windows |
| 2.1.163 | запреты на домашний каталог не блокировали команды со ссылкой через переменную |
| 2.1.186 | запреты на тип субагента не применялись при именованном запуске |
| 2.1.214 | обход проверки в сессиях PowerShell 5.1 |
Так выглядит нормальный жизненный цикл фильтра, который разбирает строки: язык оболочки богаче любого шаблона, и новые написания находятся быстрее, чем закрываются старые. Отсюда и главный вывод: перечень запрещённых строк не бывает последним слоем защиты.
Слои удобно держать в голове лестницей - и помнить, мимо чего проходит каждый:
Каждая ступень закрывает то, что не удержала предыдущая, и ни одна не закрывает всё. Нижняя - единственная, которая продолжает работать там, где вашей машины уже нет.
Что сделать сегодня
- Откройте свой список разовых согласий и посчитайте, сколько в нём записей и сколько из них разрешают запуск интерпретатора или менеджера пакетов. Если такие есть, всё точное ниже них декоративно.
- Проверьте форму записи путей в запретах.
Read(./.env)иRead(/.env)указывают в разные места, и первая почти наверняка не работает. - Замените перечисление команд на закрытие пути. Одно правило
Readна файл вместо шести правил наcat,head,tailи остальные. - Добавьте один хук
PreToolUseна то, что правило выразить не может, и проверьте, что он завершается кодом два. Код один не блокирует. - Подайте заведомо запрещённый вход и убедитесь, что вас остановили. Потом повторите вторым каналом.
- Посмотрите на токены, которые лежат в среде агента, и сузьте их область действия до того, что ему реально нужно. Если агент пишет в боевую систему, поставьте перед записью предусловие: сверить с текущим состоянием и доказать обратимость.
Список запретов держать стоит. Просто помните, из чего он сделан: это фильтр по строке команды, и живёт он в том же слое, в котором агент формулирует свои команды. Всё, что агент делает не набором строки в оболочке, проходит мимо него - и именно там обычно и происходит то, о чём потом пишут разборы.
Как измерить, что доступно агенту на машине прямо сейчас, разбирали отдельно: проверка периметра агента за 10 минут. Почему агент теряет нить задачи и при чём тут контекстное окно - в разборе про забывчивость агента.
Источники
- Configure permissions - документация Claude Code - порядок вычисления deny/ask/allow, формы записи пути, границы применимости правил, обёртки команд.
- Permission modes - документация Claude Code - шесть режимов, защищённые пути, поведение
dontAsk. - Hooks - документация Claude Code - решения хука, коды возврата, где хук не срабатывает.
- Settings - документация Claude Code - приоритет уровней настроек, слияние массивов, управляемые настройки.
- Secure deployment - документация Agent SDK - шлагбаум против песочницы, вынос секретов за границу агента.
- Джон Хьюз. How we built Claude Code auto mode - чрезмерное рвение, вырезание текста ассистента, доля пропусков 17 %.
- CHANGELOG репозитория anthropics/claude-code - построчный журнал починок обхода правил.
- Issue #6699: deny-правила не применяются - разбор формы записи пути от сотрудника вендора.
- Issue #81041: правила ask загружаются и не срабатывают - четыре формы записи, все молчат.
- Issue #20946: хук вернул код 2, коммит всё равно прошёл - асинхронные хуки в режиме обхода разрешений.
- Issue #61148: вложение файла обходит запрет и хуки.
- Issue #81862: канал выделения в редакторе обходит запрет.
- Файл настроек репозитория docker/mcp-gateway - восемь правил с пробелом перед двоеточием, живой пример мёртвой формы.
- Issue #34767: штатная команда переписывает защищённый правилом файл настроек.
- Леонардо Ди Донато. How Claude Code escapes its own denylist and sandbox - переформулировка пути, усталость от подтверждений.
- Trail of Bits, claude-code-config - открытый список запретов и связка правила с хуком.
- Томас Клабёрн. Cursor-Opus agent snuffs out startup's production database - случай PocketOS, слова основателя и гендиректора платформы.
- OWASP LLM06:2025 Excessive Agency - избыточные права как отдельный корень проблемы.
- Cursor: reference/permissions - формулировка вендора о том, что списки разрешений не граница безопасности.
- Gemini CLI: enterprise.md - приоритет белого списка над чёрным, оговорка про обход конфигурации.