AIスタッフに鍵を持たせる — Claude Codeのhook機構で実現するAI間権限管理

あるAIエージェントが、本来触れるべきでないデータベースに書き込みを実行した。別のエージェントが、プロジェクトの機密ファイルを意図せず上書きした。これらは架空の話ではなく、AIエージェントを業務に組み込む組織で実際に起きている問題です。

OWASP Agentic Top 10(2026年版)は、AIエージェント固有のセキュリティリスクとして「権限の過剰付与」を上位に挙げています。人間の従業員に対しては当然のように行うアクセス制御——「経理部は経理システムだけ」「新人にはまず閲覧権限だけ」——を、AIエージェントに対しても同じ厳密さで適用する必要があるということです。

Claude Codeには、この課題に対する実践的な回答があります。hooks(フック)機構です。hooksはLLMの判断に依存しない決定論的な制御をツール実行の前後に挟み込むことで、「AIが何をできて、何をできないか」をコードで定義します。この記事では、settings.jsonによる宣言的な権限設定から、PreToolUse hookによる動的なアクセス制御、さらにマルチエージェント環境での権限分離まで、Claude Codeの権限管理を一気通貫で解説します。

後半では、93名のAIスタッフを運用する京谷商会が実際に構築した「GTD書き込みキー」の仕組みを、設定ファイルとスクリプトの全文付きで紹介します。概念だけでなく、明日から自分のプロジェクトに適用できる具体性を重視しました。

Claude Code hooksとは何か——ライフサイクルに介入する仕組み

Claude Codeのhooksは、AIがツールを使用するライフサイクルの特定タイミングで自動実行されるユーザー定義スクリプトです。ファイルの編集、コマンドの実行、MCPサーバーへの接続——これらの操作が行われる「前」と「後」に、自分が書いたプログラムを差し込めます。

権限制御において中核となるイベントは以下の4つです。

イベント 発火タイミング 権限制御での役割
PreToolUse ツール実行の直前 実行の許可・拒否・確認要求を判定
PermissionRequest 権限ダイアログ表示時 ダイアログへの自動応答
PostToolUse ツール実行の成功後 監査ログの記録、自動整形
ConfigChange 設定ファイル変更時 不正な設定変更のブロック

このうちPreToolUseが権限管理の要です。PreToolUse hookはツールが実行される直前に発火し、標準出力にJSON形式でpermissionDecisionを返すことで、そのツール呼び出しを「許可(allow)」「拒否(deny)」「確認要求(ask)」のいずれかに振り分けます。

重要なのは、hooksが「LLMの推論」ではなく「プログラムの実行結果」に基づいて判断する点です。LLMに「このファイルを編集しないでください」とプロンプトで指示しても、プロンプトインジェクションや文脈の逸脱によって無視される可能性があります。一方、hookスクリプトがexit code 2を返せば、LLMの意思とは無関係にツール実行はブロックされます。これがhooksの本質的な強みです。

settings.jsonによる宣言的権限設定——まず「全拒否」から始める

hooksの動的制御に入る前に、土台となる静的な権限設定を理解する必要があります。Claude Codeのsettings.jsonでは、permissionsフィールドにallow(許可)、deny(拒否)、ask(確認要求)の3種類のリストを宣言的に定義します。

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Read(//**/.env)",
      "Read(//**/.env.*)",
      "Edit(//**/.env)",
      "Write(//**/.env)"
    ],
    "allow": [
      "Read",
      "Bash(npm run *)",
      "Bash(git status *)"
    ],
    "ask": [
      "Bash(git push *)"
    ]
  }
}

ルール評価の順序はdeny → ask → allowで、最初にマッチしたルールが適用されます。つまりdenyは絶対的な拒否権を持ち、いかなる下位設定やhookのallow判定でも覆すことができません。これはdeny-firstアーキテクチャの考え方——「まず全てを拒否し、必要なものだけを許可する」——そのものです。

4層の設定ファイル階層

settings.jsonは単一ファイルではなく、4つの階層で構成されています。

優先度 スコープ ファイルパス 用途
最高 Managed OS指定のシステムディレクトリ IT管理者が全社展開する絶対ルール
Local .claude/settings.local.json 個人のローカル設定(gitignore対象)
Project .claude/settings.json チーム共有のプロジェクト設定(git管理)
User ~/.claude/settings.json 個人の全プロジェクト共通設定

各レベルの権限リストは**マージ(結合)**されます。上書きではありません。Managedレベルでdenyされたツールは、ProjectやUserレベルでallowを宣言しても拒否されたままです。この設計により、組織のセキュリティポリシーを個人が緩めることは構造的に不可能になっています。

企業環境では、Managed settingsをMDM(モバイルデバイス管理)やグループポリシーで配布することで、全社員のClaude Code環境に統一的なセキュリティベースラインを適用できます。allowManagedPermissionRulesOnly: trueを設定すれば、User/Projectレベルのルールを完全に無効化し、Managed Settingsのルールだけを適用する排他モードも可能です。

設定だけでは防げない抜け道

ただし、Claude Codeの静的な権限設定には構造的な限界があります。たとえばRead(./.env)をdenyしても、Bash(cat .env)は別のツールなのでブロックされません。URLパターンによるネットワーク制限も、オプション順序の変更やリダイレクトで回避できる場合があります。

この「宣言的ルールの隙間」を埋めるのが、次に解説するPreToolUse hookです。

PreToolUse hookによる動的権限制御——コードで「鍵」を作る

PreToolUse hookは、settings.jsonの静的ルールでは表現できない「文脈に依存する権限判断」を実装する仕組みです。settings.jsonのhooksセクションに定義し、マッチするツールが呼び出されるたびにスクリプトが実行されます。

hookの基本構造

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash /path/to/your-guard-script.sh"
          }
        ]
      }
    ]
  }
}

matcherにはツール名を指定します。"Bash"ならBashコマンド実行時、"Edit|Write"ならファイル編集・書き込み時に発火します。hookスクリプトは標準入力からJSON形式でツール名と入力パラメータを受け取り、判断結果を返します。

exit codeによる3つの判断

hookスクリプトの終了コードが権限判断を決定します。

exit code 動作 用途
0 続行(標準出力のJSONで自動承認・拒否・確認の詳細制御可能) 許可、または条件付き許可
2 ブロック(標準エラー出力の理由がClaudeにフィードバック) 明示的な拒否
その他 続行(標準エラー出力はログのみ) hookスクリプト自体のエラー時

exit code 0で続行する場合、標準出力にpermissionDecisionを含むJSONを返すことでさらに細かい制御が可能です。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "GTD書き込みキーが見つかりません"
  }
}

permissionDecisionには"allow"(自動承認)、"deny"(拒否)、"ask"(ユーザー確認)の3値を指定できます。Claude CodeはこのpermissionDecisionの値に基づいて、ツール実行の可否を決定論的に判断します。ここで重要な設計原則があります。hookのpermissionDecisionで"allow"を返しても、settings.jsonのdenyルールを覆すことはできません。deny-firstの原則はhooks経由でも貫徹されます。

実装例:破壊的コマンドのブロック

以下は、rm -rfのような破壊的コマンドを検知してブロックするPreToolUseスクリプトの例です。

#!/bin/bash
input=$(cat)
command_str=$(echo "$input" | jq -r '.tool_input.command // empty')
command_upper=$(echo "$command_str" | tr '[:lower:]' '[:upper:]')

if [[ "$command_upper" == *"RM -RF"* ]] || [[ "$command_upper" == *"DROP TABLE"* ]]; then
  echo "破壊的コマンドの実行はブロックされました。" >&2
  exit 2
fi
exit 0

Claude Codeのhookスクリプトは標準入力からJSONを読み取り、jqでコマンド文字列を抽出し、パターンマッチでブロック判定を行います。exit 2を返すと、そのツール呼び出しは実行されず、標準エラー出力のメッセージがClaude Codeへのフィードバックとして伝わります。Claude Codeはこのフィードバックを受けて、代替手段を検討したり、ユーザーに状況を説明したりします。

マルチエージェント環境の権限分離——「誰が何の鍵を持つか」を設計する

Claude Codeでは、サブエージェント(Agentツール)やAgent Teams機能を使って複数のAIエージェントを並列・階層的に稼働させることができます。このマルチエージェント環境において、権限管理は「全員に同じ鍵を渡す」のではなく「役割に応じた鍵だけを渡す」設計が求められます。

サブエージェントの権限設計

サブエージェントは親エージェントから起動されますが、独自のコンテキストウィンドウとシステムプロンプトを持ちます。親のsettings.jsonのdenyルールはサブエージェントにも継承されるため、親が禁止したツールをサブエージェントが使うことはできません。

一方で、サブエージェントの「できること」は起動時のプロンプトで制御します。

あなたはGTDオペレーターです。以下の操作だけを行ってください。
1. 個人ファイルを全文Read(Grep禁止)し、命令事項セクションからキーファイルパスを取得
2. キーファイルを読み込み、GTD-WRITE-KEYを取得
3. 取得したキーをSQLコメントに含めてD1に書き込み

上記以外の操作(ファイル編集、他のデータベースへの書き込み等)は行わないでください。

しかし、プロンプトによる制御だけでは「お願いベース」に留まります。LLMは指示を誤解したり、長い文脈の中で指示を忘れたりする可能性があります。ここでhooksが「技術的なゲート」として機能します。サブエージェントがプロンプトの指示を逸脱してデータベースに書き込もうとしても、hookスクリプトが正しい認証キーの有無を検証し、キーがなければexit 2でブロックします。

プロンプトによる行動指示(ソフトウェア的制御)とhookによるアクセス検証(ハードウェア的制御)の二重構造が、マルチエージェント権限管理の基本パターンです

Agent Teamsの権限継承

Claude Code v2.0で導入されたAgent Teams機能では、複数のClaude Codeインスタンスが並列チームとして協調動作します。チームリーダーの権限設定が初期値として各メンバーに継承されますが、プロジェクト単位のsettings.jsonで個別の制限を追加できます。

MCP(Model Context Protocol)サーバーとの接続についても同様です。mcp__サーバー名__ツール名の形式でPermission Rulesに記述することで、特定のMCPサーバーや特定のツールだけを許可・拒否できます。たとえばデータベース参照用のMCPサーバーは全エージェントに許可し、書き込み用のMCPサーバーは特定のエージェントにだけ許可する、といった粒度の制御が可能です。

ゼロトラスト式AI権限設計——「信頼しない」から始める

ここまでの技術的な仕組みを、セキュリティの設計思想として整理します。

従来のネットワークセキュリティでは「社内ネットワークは信頼する」という境界型モデルが主流でした。ゼロトラストはこれを否定し、「何も信頼しない。すべてのアクセスを都度検証する」という原則を掲げます。AIエージェントの権限管理にも、このゼロトラストの考え方がそのまま適用できます。

最小権限の原則の適用。各AIエージェントには、そのタスクの遂行に必要な最小限のツールとリソースだけを許可します。最小権限の考え方をClaude Codeに適用するなら、「このエージェントはファイルの読み取りだけ」「このエージェントはnpm runコマンドだけ」という粒度で、denyリストではなくallowリストで制御するのが理想です。プロジェクトごとのCLAUDE.mdに各エージェントの責務を明記し、settings.jsonのallowリストをCLAUDE.mdの責務と一致させることで、最小権限を文書と設定の両面から担保できます。

非人間アイデンティティ(NHI)としての管理。OWASPはAIエージェントを「最も危険なNHI(Non-Human Identity)クラス」と位置づけています。人間のユーザーと同じアイデンティティ管理の枠組み——識別、認証、認可、監査——をAIエージェントにも適用すべきだという提言です。Claude Codeの文脈では、settings.jsonの4層階層とhooksによる動的検証が、この識別・認証・認可の役割を担います。

監査ログによる事後検証。ゼロトラストでは「信頼しない代わりに、すべてを記録する」ことも重要です。PostToolUse hookで全ツール実行の監査ログを記録し、ConfigChange hookで設定変更を追跡することで、事後的な検証と異常検知が可能になります。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "node /path/to/audit-log.js"
          }
        ]
      }
    ]
  }
}

ただし、AnthropicとTrail of Bitsの共同見解として明記されている通り、hooksは「ガードレール」であり「壁」ではありません。hookスクリプト自体が設定ファイルに定義されている以上、設定ファイルへのアクセス権を持つ者であれば変更可能です。CVE-2025-59536やCVE-2026-21852のように、hookの設定ファイルを経由した任意コード実行(RCE)の脆弱性も報告されています。settings.jsonの変更権限を厳格に管理し、ConfigChange hookで設定変更を監視する二重の防御が必要です。

実例:京谷商会の「GTD書き込みキー」——93名のAIスタッフを統制する仕組み

ここからは、93名のAIスタッフを運用する京谷商会が実際に構築した権限管理の事例を紹介します。抽象的な設計論ではなく、本番環境で稼働しているコードです。

課題:GTDデータベースへの書き込みを1名に限定する

京谷商会ではCloudflare D1上にGTD(Getting Things Done)データベースを運用しており、全AIスタッフのタスク管理を一元化しています。このデータベースへの書き込み権限を、GTDオペレーターであるPMQ-004(中川悠斗)だけに限定する必要がありました。

他の92名のAIスタッフがタスク登録を依頼する場合は、必ずPMQ-004をサブエージェントとして呼び出し、PMQ-004経由でD1に書き込む——この「唯一の書き込み経路」を技術的に保証する仕組みが求められました。

解決策:二重保証構造

実装は以下の二重構造で構成されています。

第1層:hookによる技術的ゲート(block-gtd-write.sh)

#!/bin/bash
# PreToolUse hook: gtd-dbへの書き込みSQL実行時にGTD-WRITE-KEYを検証

GTD_WRITE_KEY="PMQ004-KAPPA-2026Q1"

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
command_str=$(echo "$input" | jq -r '.tool_input.command // empty')

# Bash以外はスルー
if [[ "$tool_name" != "Bash" ]]; then
  exit 0
fi

# gtd-dbを対象としないコマンドはスルー
if [[ "$command_str" != *"gtd-db"* ]]; then
  exit 0
fi

# 書き込み操作を含むか判定
command_upper=$(echo "$command_str" | tr '[:lower:]' '[:upper:]')
if [[ "$command_upper" == *"INSERT"* || "$command_upper" == *"UPDATE"* || \
      "$command_upper" == *"DELETE"* || "$command_upper" == *"DROP"* ]]; then
  # 書き込み操作 → キーの存在を検証
  if [[ "$command_str" == *"$GTD_WRITE_KEY"* ]]; then
    exit 0  # キーあり → 許可
  else
    echo "GTD書き込みキー未検証。PMQ-004をサブエージェントとして呼び出してください。" >&2
    exit 2  # キーなし → ブロック
  fi
fi

# SELECT等の参照クエリはキー不要で通過
exit 0

このスクリプトは、Bashツールでgtd-dbを対象としたINSERT/UPDATE/DELETE文が実行されようとしたとき、SQLコメント内に正しいGTD-WRITE-KEYが含まれているかを検証します。キーがなければexit 2でブロックし、標準エラー出力でClaude(呼び出し元のAIスタッフ)に「PMQ-004を呼び出せ」というフィードバックを返します。

第2層:ファイル編集のブロック(block-gtd-edit.sh)

#!/bin/bash
# PreToolUse hook: GTD.mdへの直接編集をブロック

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

if [[ ("$tool_name" == "Edit" || "$tool_name" == "Write") && \
      "$file_path" == *"GTD.md"* ]]; then
  echo "GTD.mdは自動生成ファイルです。直接編集できません。" >&2
  exit 2
fi

exit 0

GTD.mdはD1データベースから自動生成されるファイルのため、Edit/Writeツールによる直接編集もブロックします。

settings.jsonのhook設定

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash /path/to/scripts/block-gtd-edit.sh"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash /path/to/scripts/block-gtd-write.sh"
          }
        ]
      }
    ]
  }
}

2つのPreToolUse hookが、それぞれ異なるmatcherで異なる経路をガードしています。"Edit|Write"マッチャーがファイル編集経路を、"Bash"マッチャーがD1コマンド実行経路を制御します。

キーの保管と取得フロー

GTD-WRITE-KEYは、PMQ-004(中川悠斗)の個人ファイルにだけ記載されています。他のAIスタッフの個人ファイルにはキーの情報は一切存在しません。

PMQ-004をサブエージェントとして起動するとき、起動プロンプトには「個人ファイルを全文Read(Grep禁止)し、命令事項セクションからキーファイルパスを取得せよ」という指示が含まれます。Grep禁止にしているのは、Grepではキーの断片だけが取得される可能性があり、個人ファイルの全文脈——命令事項、注意事項、手順——を必ず読み込ませるためです。

この設計のポイントは、技術的ゲート(hookがキーを検証する)と全文読み込み保証(サブエージェントが必ず個人ファイルの全文脈を取得する)の二重保証にあります。hookだけでは「キーを知っていれば誰でも書き込める」という抜け道が残りますが、キーの保管場所を1名の個人ファイルに限定し、起動プロンプトで全文読み込みを強制することで、「正しい手順を経たエージェントだけがキーを取得できる」仕組みになっています。

用途別hookテンプレート集

ここまでの内容を踏まえ、すぐに使えるhook設定のテンプレートを4パターン紹介します。

テンプレート1:開発環境向け——基本的な安全装置

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Read(//**/.env)",
      "Edit(//**/.env)"
    ],
    "allow": [
      "Read",
      "Edit",
      "Bash(npm run *)",
      "Bash(git status *)",
      "Bash(git diff *)"
    ],
    "ask": [
      "Bash(git push *)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/block-destructive-commands.sh"
          }
        ]
      }
    ]
  }
}

denyリストで破壊的操作と機密ファイルへのアクセスを静的にブロックし、PreToolUse hookで動的な追加検証を行う構成です。最小権限の原則に基づき、allowリストには業務に必要なコマンドだけを列挙しています。個人開発や小規模チームでの利用に適しています。

テンプレート2:チーム開発向け——ブランチ保護とログ記録

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash .claude/hooks/enforce-branch-policy.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/audit-log.js"
          }
        ]
      }
    ]
  }
}

PreToolUse hookでmain/masterブランチへの直接pushをブロックし、PostToolUse hookで全ツール実行の監査ログを記録します。チーム開発ではワークフロー自動化と監査の両立が重要です。hookのpermissionDecisionで"allow"を返せば特定のブランチ操作を自動承認し、それ以外は"ask"で確認を求めるといった柔軟な制御も可能です。

テンプレート3:本番環境向け——Managed Settingsによる全社統制

{
  "permissions": {
    "deny": [
      "Bash(rm *)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(//**/.env)",
      "Read(//**/.env.*)"
    ],
    "disableBypassPermissionsMode": "disable",
    "allowManagedPermissionRulesOnly": true,
    "allowManagedHooksOnly": true
  }
}

Managed Settingsとしてシステムディレクトリに配置し、MDM経由で全社展開します。disableBypassPermissionsMode: "disable"--dangerously-skip-permissionsフラグを無効化し、allowManagedPermissionRulesOnly: trueでUser/Projectレベルのルールを無視します。IT管理者が定義したルールだけが適用される、最も厳格な構成です。

テンプレート4:マルチエージェント向け——役割別の権限分離

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/block-protected-files.sh"
          }
        ]
      },
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash ./scripts/verify-write-key.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "node ./scripts/audit-log.js"
          }
        ]
      }
    ],
    "ConfigChange": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "node ./scripts/config-change-alert.js"
          }
        ]
      }
    ]
  }
}

京谷商会の事例を汎用化したテンプレートです。保護ファイルへの編集ブロック、データベース書き込みのキー検証、全操作の監査ログ、設定変更の監視という4層の制御を組み合わせています。verify-write-key.shの中身を書き換えることで、自社固有の認証ロジックを実装できます。

まとめ——段階的に権限を強化する

Claude Codeの権限管理は、settings.jsonの宣言的ルール、hookの動的制御、サンドボックスのOS レベル隔離という3層で構成されています。これらは補完関係にあり、単独では防げない抜け道を互いに塞ぐ設計です。

実装の優先順位としては、まずsettings.jsonのdenyリストで明らかに危険な操作をブロックすることから始めてください。次にPreToolUse hookで文脈依存の動的検証を追加し、最後にPostToolUse hookで監査ログを整備します。一度に完璧な構成を目指す必要はありません。

AIエージェントに「何でもできる全権」を渡すのは、新入社員に初日からサーバールームの鍵を渡すようなものです。必要な鍵を、必要なタイミングで、必要な人(エージェント)にだけ渡す。この当たり前の原則を、hooksというガードレールで確実に実装できることがClaude Codeの強みです。

権限管理の設計思想についてさらに深く知りたい方は、deny-firstアーキテクチャ設計ガイドAI導入 中小企業の成功事例集も参照してください。

Claude Codeのhooksとsettings.jsonの公式ドキュメントは以下で確認できます。