AI 開発

AI Coding Agent に権限制御が必要な理由:JSON Tool Call から Shell、Git、ファイルシステムの権限モデル(2026)

約 16 分

安全の稿は攻撃面を書いた。本稿は権限モデルだけだ:ツール呼び出しは JSON であり、実行権は宿主にある。Shell、Git、ファイルシステムは三つの門。「許可」を押すことは、サンドボックスが閉じたことではない。

読む前に

門 何を鎖すか 誰が実行するか
Shell コマンド文字列、複合コマンド allow / ask / deny。サンドボックスは別
Git status、diff、commit、push。hooks / config 作業域の書き込み ≠ 倉庫メタの書き込み
ファイルシステム src、.env、作業域の外 ツール規則 + OS サンドボックス
ネットワーク ドメイン、外向き 既定は閉じる。名簿を別に開く

方針は宿主が評する。モデルは JSON を埋める。門は動かさない。

権限制御が要るのは、実行権がモデルにないからだ

Coding Agent がツールを呼ぶとき、モデルが出すのは JSON だけだ。名前と引数。git status を走らせ、src/orders/A-1042.json を直すのは宿主だ。Schema は形を鎖す。権限方針は、形が通ったあと走ってよいかを鎖す。第二の門がなければ、第一は礼儀に過ぎない。

  • Schema:引数を鎖す。コマンドは文字列。経路は決めた接頭辞の下。
  • 権限方針:allow / ask / deny。公式の順は deny、ask、allow。狭い allow は広い deny を救わない。
  • サンドボックス:OS の層。注入がモデルの判断をずらしても、過程は作業域を出られない。「許可」はサンドボックスを開くことではない。

ツール JSON の埋め方は AI Coding Agent と JSON ツール呼び出し。攻撃面の収め方は Agent の安全危機と JSON 検証。Schema が門である理由は AI に JSON Schema が必要な理由。本稿は三つの話を繰り返さない。Shell / Git / ファイルを対象として落とすだけだ。

権限オブジェクト:allow、ask、deny

Claude Code は規則を settings.json の permissions に書く。評するのは宿主であり、モデルではない。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 Mode は最善の柵であり硬い境界ではないと書く。硬い境界にはサンドボックスを付ける。

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

第二の門:Git は普通の書き込みではない

作業域が書けることは、.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 サンドボックス。公式が分けるのは、止める呼び出しが違うからだ:

  • ツール規則:Claude の Read / Edit deny は組み込みのファイルツールと、cat / head など認識したコマンドを覆う。言語スクリプトが自分でファイルを開く呼び出しは、この層の外。
  • OS サンドボックス:過程の境界。既定の書き込みは作業域とセッション一時領域。外の経路は明示。ネットワークは既定で閉じる。
  • 資格ファイル:.env を deny へ。「秘密を読むな」という指示だけに頼らない。

MCP ツールは別の表だ。各ツールに門が要る。プロトコルの対比は MCP Tool Schema vs Function Calling。本稿は Coding Agent が MCP をどう呼ぶかは書かない。繋がったツールが権限オブジェクトに名前を持つことだけを求める。

権限を対象にする五手順

「慎重に実行して」から始めない。門を書く:

  1. まず deny を書く。push、hooks、config、.env、作業域の外を一度列ぶ。
  2. 読み取りコマンドを allow へ。status、diff、test。複合は副コマンドごとに一条。
  3. 書き込みと commit を ask へ。人が経路と diff を見る。編集の承認はセッションで終わり、永久規則にはならない。
  4. OS サンドボックスを付ける。Codex は :workspace。不用意に :danger-full-access にしない。Cursor の既定を Run Everything にしない。Claude の bypassPermissions は隔離容器に残す。
  5. 方針を JSON として審る。allow と deny を並べる。共有規則は版管理へ。手元の緩和は local に残す。

Schema を門にする話は Agents API + JSON Schema。本稿が求めるのは、Coding Agent の門がすべて対象であり、宿主が評することだ。

権限の逸脱を見直すなら、二つの JSON を広げる

門を越えた多くは、規則が揃っていないことだ。モデルが野になったことではない。

  • 方針とその tool_use を 整形 し、deny が当たったか allow が漏れたかを見る。
  • JSONPath 抽出 で $.permissions.deny と $.input.command を抜く。
  • 通った呼び出しと拒まれた呼び出しを JSON Diff。規則を先に直し、指示は後。
  • Git メタの規則と src の書き込みは分けて審る。A-1042.json を直せることは、hooks を直せることではない。
  • 方針ファイル、倉庫の経路、セッション記録を公開フォーマッタに貼らない。JSONSort はブラウザ内で動く。

二つの軌跡が規則で再建できて初めて、権限を集めている。「許可を押した」を集めているのではない。

よくある落とし穴

  • Schema を権限だと思う。Schema は形を鎖す。形の正しい git push も deny が要る。
  • allow だけ書く。公式の順は deny が先。deny がなければ、名簿は承認を速めるだけだ。
  • 作業域の書き込みを 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 は不要か?

要る。サンドボックスは過程が作業域を出るのを止める。deny は、その中で走ってはならないコマンド(push など)を止める。鎖す門が違う。

Git を別立てする理由は?

倉庫メタは普通のファイルではない。src を直せることは、hooks と config を直せることではない。push は外向きであり、書き込みではない。公式はこれらを「作業域が書ける」から外す。

読み取り専用で足りるか?

コードを見るには足りる。A-1042 を変えるには足りない。Codex の :read-only、Claude の plan、Cursor の読み取りサンドボックスは探索用であり、公開用ではない。

方針を Git に入れてよいか?

入れてよい。入れるべきだ。共有の deny / allow は版管理へ。手元の緩和は local に残し、コミットしない。bypass と auto を閉じるのは managed 設定。

結論

Coding Agent に権限制御が要るのは、モデルが JSON を埋めるだけだからだ。Shell、Git、ファイルシステムは三つの門。Schema を通ったあと、宿主がもう一度評する。

git push がどの deny に当たるかを先に書く。経路を指せれば、方針は検証できる。モデルは変わる。門を先に安定させる。

続きを読む

更新記録: 初回公開