AI-разработка

Зачем AI Coding Agent нужен контроль прав: Shell, Git и файловая система как JSON-политика (2026)

Около 16 мин

Текст про безопасность уже собрал поверхность атаки. Этот пишет только модель прав: вызов инструмента — JSON; исполнение сидит у хозяина. Shell, Git и файловая система — три двери. Нажать «разрешить» не значит закрыть песочницу.

Перед чтением

Дверь Что запирает Кто исполняет
Shell Строки команд, составные команды allow / ask / deny; песочница отдельно
Git status, diff, commit, push; hooks / config Писать workspace ≠ писать метаданные
Файловая система src, .env, пути вне workspace Правила инструмента + OS-песочница
Сеть Домены, исходящее По умолчанию выкл.; список открывать отдельно

Политику оценивает хозяин. Модель заполняет JSON. Дверь она не двигает.

Контроль прав нужен, потому что модель не исполняет

Когда coding agent зовёт инструмент, модель выдаёт только JSON: имя и аргументы. git status и правку src/orders/A-1042.json делает хозяин. Schema запирает форму. Политика прав запирает, можно ли гонять уже верный вызов. Без второй двери первая — вежливость.

  • Schema: закрыть аргументы. Команда — строка. Путь остаётся под оговорённым префиксом.
  • Политика прав: allow / ask / deny. Официальный порядок: deny, ask, allow. Узкий allow не дырявит широкий deny.
  • Песочница: слой ОС. Если инъекция сдвинула модель, процесс всё равно не выходит из workspace. Клик «разрешить» песочницу не открывает.

Как заполняют JSON инструмента: AI coding agent и JSON-вызовы инструментов. Как собирают поверхность атаки: Кризис безопасности агентов и проверка JSON. Почему Schema — дверь: Зачем ИИ нужен JSON Schema. Этот текст три темы не повторяет. Он только сажает Shell, Git и файлы объектами.

Объект прав: allow, ask, deny

Claude Code пишет правила в permissions внутри settings.json. Оценивает хозяин, не модель. CLAUDE.md меняет, что модель пробует. Не то, что разрешено. Вымышленный репозиторий — без настоящих секретов:

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(npm test *)",
      "Read(./src/**)",
      "Edit(./src/**)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Read(./.env)",
      "Edit(./.git/hooks/**)",
      "Edit(./.git/config)"
    ]
  }
}

Голое имя инструмента вроде Bash в deny вынимает инструмент из контекста. Правило в скобках режет подходящие вызовы и оставляет инструмент. Составные команды матчятся по подкомандам: разрешить git status не значит разрешить вторую команду следом. Codex держит профили (:workspace / :read-only) и не мешает их со старым sandbox_mode. Cursor — префикс-список в permissions.json плюс sandbox.json.

Первая дверь: Shell

Модель отдаёт строку команды. Хозяин сверяет политику, потом гоняет. Только чтение обычно проходит:

{
  "type": "tool_use",
  "id": "call_7f2a",
  "name": "Bash",
  "input": {
    "command": "git status"
  }
}

В той же политике удалённый push — deny. Объект верной формы. Исполнение остаётся у хозяина. Список терминала Cursor матчит префиксы: git кроет git status. Официально Run Modes — лучшие из возможных ограждения, не жёсткая граница. Для жёсткой границы включать песочницу.

{
  "type": "tool_use",
  "id": "call_8a01",
  "name": "Bash",
  "input": {
    "command": "git push origin main"
  }
}

Вторая дверь: Git — не обычная запись на диск

Писать в workspace — не писать в .git. Codex под записываемыми корнями держит .git, .agents и .codex только для чтения; если .git — указатель gitdir:, разобранный каталог тоже защищён. Песочница Claude Code даёт worktree писать в общий .git достаточно для commit и по-прежнему режет hooks/ и config. Cursor так же бережёт .git/hooks и .git/config. Отвергнутый push должен оставить объект:

{
  "tool": "Bash",
  "command": "git push origin main",
  "eval_order": ["deny", "ask", "allow"],
  "matched": "Bash(git push *)",
  "rule": "deny",
  "sandbox": "workspace-write",
  "execute": false
}

При провале вернуть tool_result с именем правила. Не трогать удалённое молча. commit — на ask: человек видит diff. push — на deny, пока эта сессия явно не публикация.

{
  "type": "tool_result",
  "tool_use_id": "call_8a01",
  "is_error": true,
  "content": "denied by policy: Bash(git push *)"
}

Третья дверь: файловой системе нужны два слоя

Один слой — правила инструмента. Другой — OS-песочница. Официально их разделяют, потому что они режут разные вызовы:

  • Правила инструмента: Read / Edit deny у Claude кроет встроенные файловые инструменты и узнанные команды вроде cat / head. Скрипт языка, который сам открывает файлы, вне этого слоя.
  • OS-песочница: граница процесса. Запись по умолчанию — рабочий каталог и temp сессии. Лишние пути — явно. Сеть по умолчанию выключена.
  • Файлы секретов: .env в deny. Не опираться на фразу «не читайте секреты».

Инструменты MCP — ещё одна таблица. Каждому нужна своя дверь. Граница протокола: MCP Tool Schema vs Function Calling. Этот текст не пишет, как coding agent зовёт MCP. Он требует лишь, чтобы у подключённого инструмента было имя в объекте прав.

Пять шагов к исполняемой политике

Не начинать с «пожалуйста, будьте осторожны». Писать двери:

  1. Сначала deny. Push, hooks, config, .env и пути вне workspace — один раз.
  2. Команды только чтения — в allow. status, diff, test. По правилу на каждую подкоманду в составных.
  3. Запись и commit — в ask. Человек видит путь и diff. Одобрение правки умирает с сессией.
  4. Включить OS-песочницу. Codex: :workspace, не случайный :danger-full-access. Cursor: не ставить Run Everything по умолчанию. bypassPermissions у Claude — только в изолированном контейнере.
  5. Политику смотреть как JSON. Разложить allow рядом с deny. Общие правила — в систему версий. Локальное послабление остаётся локальным.

Как Schema становится дверью: Agents API + JSON Schema. Этот текст требует лишь, чтобы каждая дверь coding agent была объектом, который оценивает хозяин.

Разбирать сбой прав — разложить два JSON-следа

Перейденная дверь обычно — кривое правило, не более дикая модель.

  • формат политику рядом с тем tool_use. Смотреть, попал deny или протёк allow.
  • извлечь JSONPath вытащить $.permissions.deny и $.input.command.
  • JSON Diff проход и отказ. Сначала правило, потом prompt.
  • Правила Git-метаданных и записи в src смотреть отдельно. Править A-1042.json — не править hooks.
  • Не вставлять файлы политики, пути репозитория и логи сессии в публичный форматтер. JSONSort работает в браузере локально.

Если оба следа собираются по правилам, вы собрали модель прав. Иначе собрали «я нажал разрешить».

Частые ловушки

  • Считать Schema правами. Schema запирает форму. Верный git push всё равно хочет deny.
  • Писать только allow. Официально сначала deny. Без deny список лишь ускоряет одобрения.
  • Считать запись в workspace записью в Git. hooks и config — другая дверь. Официальные умолчания их берегут.
  • Deny инструмента без песочницы. Скрипт, который сам открывает файлы, вне правил инструмента. Нужна граница процесса.
  • bypass / full-access / Run Everything на ноутбуке. Официально — для изолированных сред. Будни: workspace + ask.
  • Мешать старый и новый Codex. Если есть sandbox_mode, профили не действуют. Выбрать одно.

FAQ

«Разрешить и больше не спрашивать» — это вечная выдача?

Не всегда. В Claude Code долгое одобрение Bash падает в settings.local.json у корня репозитория; одобрение правки умирает с сессией. Список Cursor живёт в permissions.json. Читать написанный объект, не подпись кнопки.

Если песочница включена, deny ещё нужен?

Да. Песочница не даёт процессу выйти из workspace. deny режет команды, которые внутри гонять нельзя, например push. Слои запирают разные двери.

Почему Git — отдельная дверь?

Метаданные репозитория — не обычный файл. Править src — не править hooks и config. push — исходящее, не запись на диск. Официальные продукты вынимают это из «workspace можно писать».

Хватает ли только чтения?

Чтобы смотреть код. Чтобы менять A-1042 — нет. Codex :read-only, Claude plan и песочница только чтения у Cursor — профили разведки, не публикации.

Можно ли класть политику в Git?

Можно и нужно. Общий deny / allow — в систему версий. Локальное послабление остаётся локальным и не коммитится. Managed-настройки выключают bypass и auto.

Вывод

Coding agent нуждается в контроле прав, потому что модель только заполняет JSON. Shell, Git и файловая система — три двери. После Schema хозяин оценивает ещё раз.

Сначала запишите, на какой deny сядет git push. Если можете указать путь, политика проверяема. Модели сменятся. Двери должны устояться раньше.

Дальше

Журнал изменений: Первая публикация