Разрешения в командной строке описываются короткими токенами, и это удачное решение: политика читается как список действий, а не как абзац прозы. Пять типов покрывают всё, что агент может сделать наружу: команда оболочки, чтение файла, запись файла, сетевой запрос и вызов инструмента внешнего сервера. Запрет всегда сильнее разрешения, а относительные пути считаются от рабочей области - абсолютные могут указывать и за её пределы.
Наивная политика выглядит великодушно: разрешить всё, что понадобится, чтобы не мешало. Она превращает конфигурацию в декорацию. Разрешение на любые сетевые запросы и любую запись означает, что политики фактически нет, а есть строка, создающая ощущение контроля. Ценность списка в его узости: он должен называть конкретные команды, конкретные пути и конкретные адреса.
Полезно один раз свести типы токенов в таблицу с примерами. Ниже такая карта: команда с необязательным шаблоном аргументов, чтение и запись по маскам путей, сетевой запрос по домену, инструмент внешнего сервера по имени. Обратите внимание на разницу между именем команды и шаблоном её аргументов - это тот же приём границы слова, что и в других инструментах: без него разрешение расползается на однокоренные команды.
| Токен | Пример | Что контролирует |
|---|---|---|
| Shell(команда) | Shell(git), Shell(curl:*) | Имя команды и необязательный шаблон аргументов |
| Read(путь) | Read(src/**/*.ts) | Чтение файлов |
| Write(путь) | Write(package.json) | Создание и изменение файлов |
| WebFetch(домен) | WebFetch(docs.github.com) | Сетевые запросы к точному домену или шаблону |
| Mcp(сервер:инструмент) | Mcp(linear:search*) | Конкретные инструменты внешнего сервера |
Полезно и увидеть цельный блок политики. Ниже - разрешения на работу с git, тесты пакетного менеджера, чтение и запись в исходниках, обращение к одному домену документации и один инструмент внешнего сервера; запреты - на чтение файлов переменных окружения, запись в ключи и удаление. Такой блок объясним в ревью построчно, и это главный критерий хорошей политики.
Шаблон - это полномочие, а не удобство. Разрешение вида любой сетевой адрес, любой инструмент любого сервера, запись куда угодно или произвольные аргументы для утилиты загрузки открывают канал данных наружу. Именно так выглядит типичная дыра: она не создаётся злым умыслом, а появляется из желания не возвращаться к настройке. Правильный порядок - сначала перечислить реальные команды и адреса, а расширять по мере доказанной необходимости.
Приоритет запрета над разрешением стоит использовать сознательно, а не считать техническим правилом разрешения конфликтов. Разрешения описывают работу и потому неизбежно широки: исходники целиком, все команды системы контроля версий. Запреты описывают данные, которых не должно касаться ничто: файлы переменных окружения, ключи, каталоги с выгрузками. Сузить широкое разрешение сложно и легко забыть, а один запрет действует поверх всех текущих и будущих разрешений - и это надёжнее.
Отдельная тонкость - относительные и абсолютные пути. Относительные привязаны к рабочей области, и это делает политику переносимой между машинами. Абсолютные могут указывать за её пределы, что иногда нужно - например, к соседнему пакету со схемами, - но каждый такой пункт заслуживает отдельного решения: он расширяет область действия за границу проекта, ради которого политика писалась, и на другой машине может указывать совсем не туда.
Растёт политика в одну сторону: от отказа к разрешению. Агент упирается в запрет, вы видите конкретную команду и добавляете строку под неё, а не под класс похожих. У каждой строки есть повод, и пока повод помнится, политика живая. Признак того, что она начала гнить, - строка, происхождение которой никто не объясняет: её боятся удалить, потому что вдруг что-то сломается, и она остаётся навсегда, расширяя права без причины.
Инженерный вывод простой: политика разрешений - это код, который читают. Она должна помещаться на экран, состоять из понятных строк и объясняться в двух словах на каждую. Если политику невозможно прочитать за минуту, её не будут ревьюить, а значит, через месяц она перестанет отражать реальность и превратится в набор исторических разрешений.
Типичные провалы предсказуемы. Открыть сеть целиком вместо перечисления доменов. Разрешить утилите загрузки произвольные аргументы и получить канал наружу. Смешать в одном списке разрешения проекта и личные исключения. И написать политику, которую никто не может объяснить построчно.
{
"permissions": {
"allow": [
"Shell(git)",
"Shell(pnpm:test*)",
"Read(src/**)",
"Write(src/**)",
"WebFetch(cursor.com)",
"Mcp(linear:search*)"
],
"deny": [
"Read(.env*)",
"Write(**/*.key)",
"Shell(rm)"
]
}
}