コンテンツへスキップ

機械翻訳です。正式な情報源は英語版であり、ネイティブによる確認は未完了です。

Claude Code

フックPEP内: Claude Code内のdeny-closedポリシー

著者 Olivares AI 8 分で読めます

プラットフォーム チームは、エンジニアリング組織全体で Claude Code を有効にします。ツール呼び出しにポリシーを適用するために、フック サーバー (標準入力でフック イベントを受信し、標準出力で JSON 決定を返すローカル プロセス) を接続します。最初の1週間は大丈夫なようです。 PreToolUse はすべてのツール呼び出しの前に起動され、本番環境に関わるものに対してフックは deny を返します。チームは強制が機能していると信じています。

その後、Claude Code が新しいリリースを出荷します。 PermissionRequestPreToolUse と一緒に発射を開始します。フック サーバーは、すでに返していたのと同じ permissionDecision JSON を返します。 Claude Code は応答を受け入れ、解析し、そのイベントに対して認識できるフィールドを見つけず、決定が与えられなかったかのように続行します。拒否は黙って無視されます。チームの執行ポイントは現在、gap が含まれる壁になっていますが、ログにはそのようなことは何も記載されていません。

これは、ガバナンスされた PEP が解決しなければならない問題です。Claude Code のフック ライフサイクルは、1 つのワイヤー形式を持つ 1 つのイベントではありません。これは approximately 30 個のイベントであり、それぞれランタイムが優先する異なる出力スキーマを持ち、スキーマ内の間違いは沈黙と区別できません。

ワイヤーメカニズムはセキュリティ契約です

単一の permissionDecision フィールドがどこでも機能しない理由は、Claude Code のフック イベントが独立して進化したためです。ツール呼び出しをゲートする出力シェイプは、プロンプト送信をゲートするシェイプではなく、タスクを停止するシェイプでもありません。

6 つの異なるワイヤー メカニズムがあります。

仕組み出力形状イベント
permissionDecisionhookSpecificOutput.permissionDecision (/deny/ask/defer を許可)プレツールの使用
permissionBehaviorhookSpecificOutput.decision.behavior (/deny を許可)許可リクエスト
topLevelDecisionトップレベル decision: "block" + reasonUserPromptSubmit、UserPromptExpansion、PreCompact、ConfigChange、PostToolBatch
continueFalsecontinue: false + stopReasonTaskCreated、TaskCompleted、TeammateIdle
postToolUsedecision: "block" (さらなる処理をブロックします。ツールはすでに実行されています)ポストツール使用
neutral強制可能なブロックはありませんすべてのコンテキスト/observeイベント、およびStopなどの反転イベント

PEP が PermissionRequest イベントで permissionDecision: "deny" を返した場合、Claude Code はそれを無視します。そのイベントは permissionDecision ではなく、decision.behavior を予期します。 JSON は有効で、HTTP 応答は 200 で、拒否は存在しません。これは理論上の特殊なケースではありません。これは文書化された電信契約であり、code.claude.com/docs/en/hooks に対して検証されています。

3 方向の分類: ゲート、コンテキスト、観察

すべてのフック イベントがアクションをゲートできるわけではありません。 SessionEnd または Notification を拒否しようとしても無意味です。これらのイベントは情報提供であり、意思決定を制御するものではありません。 Stop イベントを拒否しようとすることは、無意味であるよりも悪質です。Stop 上の decision: "block" は、エージェントを実行し続けます。これは、安全停止の逆です。

PEP は、認識されたすべてのフック イベントを次の 3 つのカテゴリのいずれかに分類します。

ゲーティング イベントには、アクションを許可、拒否、ブロックする決定が含まれます。それらは執行面です。しかし、すべてのゲーティング イベントが同様に強制できるわけではありません。StopSubagentStop は、Claude Code 独自の分類法ではゲーティングとして分類されますが、それらのブロック セマンティクスは反転されている (ブロック = 実行を継続する) ため、PEP はこれらを中立として扱います。オペレーターの意図に反してエージェントを存続させるブロックを発行することはありません。同様に、ElicitationElicitationResult にはゲート分類がありますが、現在のバージョンでは有線アクション メカニズムが欠如しているため、PEP は、強制が存在しない場合に適用が存在するかのように振る舞うのではなく、デフォルトでニュートラルに設定します。

コンテキスト イベントにより、PEP は additionalContext を挿入したり、出力を書き換えたりできますが、アクションを完全にブロックすることはできません。 PostToolUse は partial 例外です。フラグが設定された出力のさらなる処理をブロックできますが、ツールはすでに実行されています。残り (PermissionDeniedMessageDisplaySessionStartSetupSubagentStartPostCompactInstructionsLoadedPostToolUseFailure) はコンテキスト付き観察です。モデルのビューを停止するのではなく、モデルのビューを強化するのに役立ちます。

観察 イベント (NotificationSessionEndStopFailureCwdChangedFileChangedWorktreeCreateWorktreeRemove) には、決定制御がまったくありません。 PEP はそれらをテレメトリ パスと SIEM インベントリに記録し、中立的に応答し、次に進みます。

この分類は勧告ではありません。これは、構成ルート ディサイダーがポリシー ルールを適用するか (ゲート + 強制可能)、コンテキストを挿入するか (コンテキスト)、または単に観察するか (観察) を決定します。間違った解釈をすると、誤った強制 (無視される拒否を返す) または強制の欠如 (ゲート イベントを観察として扱う) のいずれかを意味します。

HookSpecs マップ: 唯一の信頼できる情報源

分類、ワイヤーメカニズム、強制可能フラグは 1 つのマップ内に存在します。これは、コネクタが使用する実際のコードです。HTTP 応答レンダラーと構成ルート ディサイダーの両方がそこから読み取ります。

type hookSpec struct {
    class       string   // "gating" | "context" | "observe"
    mech        hookMech
    enforceable bool
}

var hookSpecs = map[string]hookSpec{
    // GATING — フックの戻り値でアクションを allow/deny/block できる。
    "PreToolUse":          {"gating", mechPermissionDecision, true},
    "PermissionRequest":   {"gating", mechPermissionBehavior, true},
    "UserPromptSubmit":    {"gating", mechTopLevelDecision, true},
    "UserPromptExpansion": {"gating", mechTopLevelDecision, true},
    "PreCompact":          {"gating", mechTopLevelDecision, true},
    "ConfigChange":        {"gating", mechTopLevelDecision, true},
    "PostToolBatch":       {"gating", mechTopLevelDecision, true},
    "TaskCreated":         {"gating", mechContinueFalse, true},
    "TaskCompleted":       {"gating", mechContinueFalse, true},
    "TeammateIdle":        {"gating", mechContinueFalse, true},
    "Stop":                {"gating", mechNeutral, false}, // 反転
    "SubagentStop":        {"gating", mechNeutral, false}, // 反転
    "Elicitation":         {"gating", mechNeutral, false}, // v1 では未接続
    "ElicitationResult":   {"gating", mechNeutral, false},
    // CONTEXT — additionalContext / 出力の書き換え、実際のブロックはなし。
    "PostToolUse":        {"context", mechPostToolUse, true},
    "PostToolUseFailure": {"context", mechNeutral, false},
    "PermissionDenied":   {"context", mechNeutral, false},
    "MessageDisplay":     {"context", mechNeutral, false},
    "SessionStart":       {"context", mechNeutral, false},
    "Setup":              {"context", mechNeutral, false},
    "SubagentStart":      {"context", mechNeutral, false},
    "PostCompact":        {"context", mechNeutral, false},
    "InstructionsLoaded": {"context", mechNeutral, false},
    // OBSERVE — 意思決定の制御はなし。
    "Notification":   {"observe", mechNeutral, false},
    "SessionEnd":     {"observe", mechNeutral, false},
    "StopFailure":    {"observe", mechNeutral, false},
    "CwdChanged":     {"observe", mechNeutral, false},
    "FileChanged":    {"observe", mechNeutral, false},
    "WorktreeCreate": {"observe", mechNeutral, false},
    "WorktreeRemove": {"observe", mechNeutral, false},
}

30 件のイベント。それぞれに 1 つの分類、1 つのワイヤー メカニズム、および 1 つの強制可能性の判定が含まれます。レンダラーは hookMechFor(event) を参照して、どの JSON 形状を出力するかを決定します。ディサイダーは HookEnforcementFor(event) を参照して、ポリシー ルールを適用するか、コンテキストを挿入するか、監視するかを決定します。どちらも同じ地図から読み取っているので、意見が異なるはずはありません。

Deny-closed フック PEP: イベント分類とワイヤー メカニズム フロー

拒否クローズのデフォルト

ファイル内の最も重要な関数は 4 行の長さです。

func hookSpecFor(event string) hookSpec {
    if s, ok := hookSpecs[event]; ok {
        return s
    }
    return hookSpec{class: "unknown", mech: mechPermissionDecision, enforceable: true}
}

マップにないイベント (Claude Code がコネクタがまだ分類していない新しいフック イベントを出荷したため) は、unknown として扱われ、mechPermissionDecision ワイヤー メカニズムが割り当てられ、強制可能としてマークされます。これは拒否クローズのデフォルトです。認識されないイベントは許可ゲートであり、サイレント パススルーではありません。一致するポリシー ルールがない場合は、オペレータが設定したデフォルト ポスチャが適用されますが、管理された展開ではこれは拒否されます。

別の方法 (デフォルトでは中立または監視) では、Claude Code が導入するすべての新しいフック イベントは、誰かが気づいてマップに追加するまで制御されないことを意味します。 Deny-Closed モデルでは、たとえ分類が保守的であっても、新しいイベントは発生した瞬間から制御されます。新しいイベントに対する誤った拒否は表示され、修正可能です。サイレント許可は目に見えず、数か月間持続する場合があります。

HookEnforcement 構造体はこの分類をエクスポートするため、ディサイダーは 3 つの層のゲート イベントを区別できます。

  • クラシック ゲート (PreToolUsePermissionRequestPostToolUse、および不明なイベント): 一致する管理ルールがない場合、オペレーターのポリシーのデフォルトが適用されます (拒否クローズ)。
  • ライフサイクル ゲート (UserPromptSubmitTaskCreated などの他の強制可能なゲート イベント): ディサイダーは代わりにイベントごとの安全なデフォルトを適用します。UX/lifecycle イベントには中立、状態変異イベントには拒否です。
  • 強制不可能なゲート (StopSubagentStopElicitation): 決定者は関係なく中立を返します。 Claude Code が「実行を続ける」と解釈するような拒否を発行することは、安全とは逆になります。

分布: 分類からフリートまで

イベントを正しく分類できれば半分です。もう 1 つは、すべての Claude Code インスタンスに PEP を取得することです。管理設定コネクタは、フック構成を Claude Code が期待する形状にレンダリングし、サーバー管理設定ファイル (コントロール プレーンがすべての管理対象ホストにプッシュするオーバーライド不可能な構成) として配布します。

PEP フックは空のマッチャー (すべてのツールに一致します。強制ポイントを回避するツールはありません) とともに配布され、開発者のローカル フックがマネージド PEP を侵害するのを防ぐために allowManagedHooksOnly と組み合わせます。このフラグがないと、ユーザーレベルのフックがマネージドフックをシャドウイングする可能性があり、強制はバイパス可能であるにもかかわらず存在しているように見えます。オーサリング時の検証はこれを検出します。ポリシーが allowManagedHooksOnly なしで PreToolUse PEP フックを出荷する場合、コンソールは改ざん防止アドバイザリを生成します。

テレメトリ パスは別個であり、プラン非ゲートです。Claude Code の OpenTelemetry エクスポートは、管理された環境変数 (CLAUDE_CODE_ENABLE_TELEMETRYOTEL_* エクスポーター キー) を介して有効になるため、コントロール プレーンは推論をプロキシしたりサブスクリプション資格情報に触れたりすることなく、サブスクリプションの使用を監視できます。コンテンツ キャプチャ (OTEL_LOG_USER_PROMPTSOTEL_LOG_TOOL_CONTENT) はデフォルトでオフになっています。これをオンにすることは、プロンプトとツールのコンテンツを開発者のマシンから出荷し、コントロール プレーンが所有する必要がある常駐と編集義務を作成するため、意図的なフラグ付きの選択です。

これが管理された展開にとって何を意味するか

管理された PEP を使用して Claude Code を実行しているチームは、重要なプロパティをいくつか取得します。

  1. サイレント許可はありません。 すべてのフック イベントは分類され、すべての認識されないイベントはデフォルトで拒否されます。新しい Claude Code リリースでは、管理されていないライフサイクル イベントを導入することはできません。
  2. イベントごとの正しいワイヤー形状。 PEP は汎用の JSON オブジェクトを返さないので、Claude Code がそれを尊重することを望みます。間違ったスキーマでの拒否は拒否ではないため、特定のイベントが予期する正確な出力スキーマを返します。
  3. 正直な非強制。 強制できないイベント (監視イベント、反転ゲート イベント) は、強制されたふりをしません。 PEP はそれらを記録し、中立を返し、オペレーターに誤った制御感を与えません。
  4. 配布時の改ざん防止 管理設定レイヤーは、PEP フックがオーバーライド不可能であることを保証し、アンダーカットされる可能性のある構成にフラグを立てます。

PEP は、より大規模な管理された運用モデルの施行の半分です。アクセス マップ、監査台帳、およびその周りのコードとしてのポリシー層については、製品概要 および フック ドキュメント で説明されています。テレメトリ パスと許可ゲートがどのように構成されているかを確認したい場合は、アーキテクチャの概要 で両方を詳しく説明します。

関連記事

よくある質問

PEP が不明なフック イベントをデフォルトで許可ではなく拒否するのはなぜですか?

認識されないイベントに対するサイレント許可は、将来のリリースで出荷される新しいフック Claude Code は、誰かが分類マップに手動で追加するまで強制をバイパスすることを意味します。これは、管理された展開に必要なセキュリティ体制とは逆です。拒否クローズのデフォルトでは、不明なイベントが許可ゲートとして扱われます。イベントは監視され、ドロップされることはなく、一致するポリシー ルールがない場合は拒否されます。これにより、新しいライフサイクル イベントは、誰かがイベントが存在しないことに気づいた瞬間からではなく、出現した瞬間から管理されることが保証されます。

PEP がフック応答に対して間違ったワイヤー形状を出力した場合はどうなりますか?

Claude Code はそれを黙って無視します。各フック イベントは特定の出力スキーマを受け入れます。PreToolUse は hookSpecificOutput.permissionDecision を想定し、PermissionRequest は hookSpecificOutput.decision.behavior を想定し、その他のゲート イベントはトップレベルの決定または続行フィールドを想定します。 PEP が有効な JSON オブジェクトを返しても、そのイベントのスキーマが間違っている場合、Claude Code はそれを決定なしとして扱い、続行します。これが、ワイヤ メカニズムが表面的なものではなくセキュリティ コントラクトの一部である理由です。Claude Code が無視する拒否は拒否ではありません。

エージェントが到達できる範囲を可視化

Olivares AIは、お客様のAI環境のためのオープンなセルフホスト型プラットフォームです。ご自身のインフラ上に展開すれば、セキュリティチームやプラットフォームチームがこれまで求めてきたアクセスマップが手に入ります。