メインコンテンツまでスキップ

Agent SDK の権限: 6 ステップの順序

上級
What you'll learn
  • Agent SDK がすべてのツール呼び出しに対して順に辿る 6 つのステップ
  • 拒否しかできないステップ、許可しかできないステップ、両方できるステップ。残りを明白にするメンタルモデル
  • 早い段階で承認されたツールが canUseTool コールバックに届かない理由と、すべての呼び出しでチェックを走らせたいときに代わりに使うもの
  • allowedTools が鍵に見えて鍵ではない 5 つの構成
  • ツールを名指しする場合と、その内部のパターンに絞る場合で、拒否ルールがどう違うか
  • サブエージェントが何を継承するか、そして継承を拒否する唯一のモード
  • どのルールもモードもフックも上書きできないサーキットブレーカー

多くの人は、エージェントの権限をファイアウォールのように設定します。ルールを書いて、最も具体的なものが勝つと考えるのです。Claude Agent SDK はそう動きません。すべてのツール呼び出しに対して固定された 6 ステップのパイプラインを辿り、各ステップは異なる権限を持ちます。ブロックしかできないもの、承認しかできないもの、どちらもできるのは 2 つだけです。

順序を間違えると、ロックダウンされているように見えて実はそうでないエージェントを出荷することになります。これはハーネスのバグで最も一般的な種類で、ルールのタイプミスであることはほとんどありません。

6 つのステップ​

Guided walkthrough1 of 6
  1. あなたの PreToolUse フックが、他のすべてのチェックの前に最初に実行されます。フックの拒否は最終的で、bypassPermissions モードでも呼び出しをブロックします。一方、フックの allow は最終的ではありません。ステップ 2 と 3 がなお実行されます。

すべてに筋を通すモデル​

優先順位は忘れてください。代わりにこう問います。このステップには何ができるのか。

ステップ拒否できる承認できるコールバックへ回せる
1. Hooksはい — 最終的、bypassPermissions でも有効はい、ただしステップ 2〜3 はなお実行される—
2. Deny rulesはい — 最終的、bypassPermissions でも有効いいえ—
3. Ask rulesいいえいいえはい — bypassPermissions でも有効
4. Permission modeいいえはい(bypassPermissions、acceptEdits)はい(plan、書き込みに対して)
5. Allow rulesいいえはいいいえ
6. canUseToolはいはい—

この表から三つの帰結がそのまま導かれ、ほぼすべての意外な挙動を説明します。

  • 拒否はすべてに勝つ。 拒否ルールとフックによる拒否はモードより上流にあるので、それを解除するモードはありません。能力を取り除くために頼れる唯一の仕組みです。
  • 許可は何にも勝たない。 許可ルールはモードの下のステップ 5 にあります。寛容なモードを制限することはできず、本来なら確認が出る作業を事前承認するだけです。
  • コールバックは関門ではなく最後の手段。 それより上で何も解決しなかった呼び出しに対してのみ実行されます。その中に置いたチェックはすべて、早い段階で承認されたものについてはスキップされます。
Watch out

canUseTool の内部にセキュリティチェックを書いているなら、置き場所が間違っています。許可ルール、acceptEdits、bypassPermissions によって承認された呼び出しはコールバックに届きません。チェックが失敗するのではなく、単に実行されないのです。すべての呼び出しに適用しなければならないチェックは、ステップ 1 で実行され、その拒否が bypassPermissions でも有効な PreToolUse フックに置きます。

allowedTools が鍵ではない 5 つのケース​

allowedTools は許可リストのように読めますが、事前承認リストです。リストから外したものも Claude は引き続き使えます。単にモード、次いであなたのコールバックへ素通りするだけです。具体的な 5 つの罠です。

1. bypassPermissions を制約しない​

最大の問題です。リストにないツールはどの許可ルールにも一致しないのでステップ 4 に素通りし、そこで bypassPermissions が承認します。

ロックダウンされているように見える。Bash、Write、Edit を承認する。

// WRONG — bypassPermissions approves everything unlisted
const options = {
allowedTools: ["Read"],
permissionMode: "bypassPermissions"
};

bypassPermissions が必要で、特定のツールだけ取り除きたい場合、効く手段は disallowedTools だけです。モードの上流のステップ 2 に位置するからです。

2. dontAsk が許可リストを厳密にするモード​

「これらのツールとそれ以外はなし」を実現するには、事前承認を、確認する代わりに拒否するモードと組み合わせます。

本当に読み取り専用のエージェント

const options = {
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "dontAsk"
};

リストにあるツールは実行されます。確認が出るはずだったそれ以外はすべて拒否され、canUseTool は呼ばれません。リストになくても実行されるものに注意してください。読み取り専用の Bash コマンド、作業ディレクトリ内のファイル読み取り、実行前に確認しない Agent のようなツールです。これらをリクエストから完全に取り除くには、その素の名前を disallowedTools に追加します。

3. 素の名前はツール全体を承認する​

カバー範囲はエントリの形で決まり、その差は大きいです。

エントリ事前承認されるもの
"Read"すべての Read 呼び出し(ネットワークパスからの読み取りを除く)
"Bash(npm test *)"一致する Bash 呼び出しのみ。他の Bash 呼び出しは引き続きコールバックへ素通りする

スコープの指定がすべてです。許可リスト上の "Bash" は "Bash(npm test *)" とはセキュリティ上の姿勢が違い、型システムはそれを教えてくれません。

4. アンカーのないグロブは黙って無視される​

許可ルールがツール名のグロブを受け付けるのは、リテラルの mcp__<server>__ プレフィックスの後だけで、サーバー部分にはグロブを含められません。

エントリ効果
"mcp__puppeteer__*"動作する — puppeteer サーバーのすべてのツール
"mcp__github__get_*"動作する — そのサーバーの get_ ツール
"*" または "mcp__*"無視される。 何も事前承認しない

最後の行は静かな方向に倒れて失敗します。権限が増えるのではなく確認が増えるだけです。ただし包括的な承認を期待して書いたなら、午後を丸ごと費やすことになります。

5. TypeScript は警告するが、聞いていないチャンネルで​

SDK がコールバックを覆い隠すと分かっている構成で canUseTool コールバックを渡すと、コード CLAUDE_SDK_CAN_USE_TOOL_SHADOWED の Node プロセス警告が一度だけ出ます。トリガーは二つの構成です。permissionMode: "bypassPermissions" と、"Read" のような素の allowedTools の各エントリです。

覆い隠し警告を自分のログに出す

process.on("warning", (w) => {
if (w.code === "CLAUDE_SDK_CAN_USE_TOOL_SHADOWED") {
  console.error("[harness] canUseTool will be skipped:", w.message);
}
});

Bash(ls *) のようなスコープ付きエントリや acceptEdits モードはこれを発火させず、設定ファイル由来の許可ルールはこのチェックから見えません。したがって警告が出ないことは、コールバックに到達できる証明にはなりません。

拒否ルール: ツールを名指しする場合と、その内部に絞る場合​

拒否エントリは形によって挙動が違い、その差は Claude が何をできるかだけでなく、何が見えるかにあります。

エントリ効果
disallowedTools: ["Bash"]Bash のツール定義がリクエストから取り除かれる。Claude はそのツールを見ることがなく、試みることもできない
disallowedTools: ["Bash(rm *)"]Bash は引き続き使える。rm * に書かれたとおり一致する呼び出しは、あらゆるモードで拒否される。/bin/rm を含む他の Bash 呼び出しは素通りする
disallowedTools: ["*"]すべてのツール定義が取り除かれる。ツール名のグロブは拒否ルールでサポートされる: "mcp__*" はすべてのサーバーのすべての MCP ツールに一致する

押さえておくべきことが二つあります。第一に、素の形のほうが厳密に強力です。コンテキストからツールを取り除くほうが、その呼び出しを拒否するより優れています。Claude に見えないツールは、失敗した試行も、別の経路での再試行も生まないからです。第二に、Bash(rm *) は書かれた文字列に一致します。/bin/rm の抜け穴はバグではなく、文字列ベースのルールの文書化された適用範囲です。スコープ付きの Bash 拒否は、明白なものに対するガードレールとして扱い、決してセキュリティ境界として扱わないでください。

パスルール: 4 つのアンカーと 1 つの死文​

スコープ付きの Read と Edit ルールはパスパターンを取り、二つの細部が誰もがつまずくところです。

  • Edit(path) は、ファイルを書き込むすべての組み込みツールを管理する。Write と NotebookEdit も含みます。Write(path) ルールはファイル権限チェックにまったく一致しません。Write(//secrets/**) と書いたなら、何も書いていないのと同じです。
  • ダブルスラッシュはファイルシステムを意味する。 Edit(//secrets/**) は、ディスク上の /secrets 以下のあらゆる場所への書き込みをブロックします。先頭が単一のスラッシュだと、代わりにルールの出所にアンカーされます。allowedTools や disallowedTools 経由で渡されたルールでは、それはセッションの作業ディレクトリなので、Edit(/secrets/**) はディスク上の /secrets を保護しません。

実際のファイルシステムのパスへの書き込みをブロックする

// // = absolute filesystem path. One slash anchors at the rule's source.
const options = {
disallowedTools: ["Edit(//etc/**)", "Edit(//secrets/**)", "Read(//secrets/**)"]
};

モードと、それぞれが実際に買うもの​

Enter キーまたはスペースキーでカードを裏返します。左右の矢印キーでカードを移動できます。用語を表示しました。
1 / 6

本番で噛みつくので、押さえておく価値のある挙動が二つあります。

auto モードはほぼ静かに劣化する。 setModel() や applyFlagSettings() で auto が対応していないモデルに切り替えると、セッションは default モードに変わります。サブタイプ status、permissionMode: "default" の system メッセージ(TypeScript では SDKStatusMessage と型付けされる)が届き、以降の呼び出しはコールバックに届きます。モデルを元に戻しても auto は復元されません。setPermissionMode("auto") をもう一度呼ぶ必要があります。(v2.1.299 以降。)

サブエージェントは継承するが、一つだけ拒否する。 サブエージェントは、その AgentDefinition に permissionMode を設定し、かつ親が default、dontAsk、plan のいずれかである場合を除き、親の権限モードで動作します。その場合でも、Claude Code は AgentDefinition の "bypassPermissions" の値を決して適用しません。サブエージェントが bypassPermissions で動くのは、親セッション自体がそうであるときだけです(v2.1.267 以降)。この例外があるのは、サブエージェントがメインのエージェントとは異なるシステムプロンプトとより緩い挙動を持つことが多く、bypass を継承すると、ハーネスの中で最も監督の薄い部分に完全な自律的システムアクセスを渡してしまうからです。サブエージェントを参照してください。

何も上書きできないサーキットブレーカー​

クリティカルパスを対象にした rm と rmdir の削除は、許可ルールにも、allow を返す PreToolUse フックにも、決して承認されません。クリティカルパスとは、ファイルシステムのルート、ルート直下のすべての子(/usr、/etc、/data)、ホームディレクトリ、Windows のドライブルートとその最上位ディレクトリ、そして作業ディレクトリとその親です。一致する拒否ルールはコマンドを完全にブロックし続けます。要点は、どの承認経路もそれを通せないということです。

代わりに何が起きるかはモードによります。

モードクリティカルパスの削除の結果
default、acceptEdits、bypassPermissions承認を求める
autoターミナルで時間制限付きで求める。それ以外では、すべての Agent SDK セッションを含め、拒否する
dontAsk拒否する

真ん中の行がハーネスにとって重要です。auto モードの Agent SDK セッションは、クリティカルパスの削除をコールバックを呼ばずに拒否し、代わりに削除したかったものを報告するよう Claude に伝えます。エージェントが実行していない削除を突然語り始めたなら、原因はこれであって、モデルの劣化ではありません。

このチェックは、未設定の変数がルート削除に展開されうる構造の内側にも及びます。rm -rf "$DIR"/*、$1 を束縛するものがない rm -rf "$1"/*、D=$(pwd); rm -rf "$D"、そしてサブシェル、コマンド置換、bash -c スクリプト内にネストされた削除です。文書化された対策は、シェルがルートを削除する代わりに空の場合にエラーになるよう、展開をガードすることです。

グロブによる削除をクリティカルパスのチェックに通す

# Flagged: expands to a removal from / when DIR is unset
rm -rf "$DIR"/*

# Passes: :? makes the shell exit with an error instead
rm -rf "${DIR:?}"/*

どのモードも自動承認しないその他のもの​

クリティカルパスの削除以外にも、次のものは寛容なモードを常にすり抜けます。ほとんどのモードでコールバックに届き、dontAsk では確認しないため拒否されます。

  • 明示的な ask ルールに一致したツール
  • AskUserQuestion、およびサーバーが _meta["anthropic/requiresUserInteraction"] を設定する MCP ツール(v2.1.199 以降)
  • 組織が ask に設定したコネクタツール。コールバックは理由 Your organization requires approval for this tool を受け取ります
  • ネットワークパスからの読み取り。どの許可ルールも承認しません

つまり「bypassPermissions を設定したのに、なぜまだ確認されるのか」には、バグ報告に手を伸ばす前に四つの答えがあります。そして TypeScript SDK には五つ目の手段があります。permissionPrompts: "none"(v2.1.259 以降)は、ステップ 6 でのコールバックの呼び出しを完全に止めます。PermissionRequest フックには引き続き機会があり、それが判断を見送れば呼び出しは拒否されます。

二つのレシピ​

すべての呼び出しで本当に実行されるゲート​

チェックが無条件でなければならない場合(監査ログ、シークレットパスのスキャナー、コスト上限)は、ステップ 1 に置きます。

PreToolUse フックにはブロックする方法が二つあり、どちらも SDK の外でも実行できるコマンドフックで使えます。終了コード 2(stderr に書いた内容が Claude に見える理由になる)と、permissionDecision が "deny" で permissionDecisionReason を伴う stdout の JSON です。

PreToolUse フック: 唯一の無条件ゲート

# Runs before deny rules, ask rules, the mode, and allow rules.
# A deny here survives bypassPermissions.
# Wire it to hooks.PreToolUse in settings.json, or pass it in the SDK's hooks option.
jq -e '.tool_input | tostring | test("AKIA|BEGIN PRIVATE KEY|ghp_")' >/dev/null \
&& { echo "secret material in tool input" >&2; exit 2; } || exit 0

TypeScript と Python の SDK が受け付けるインプロセスのコールバック形式は、終了コードではなく同じ判断フィールドを返します。出荷する前に、お使いの SDK バージョンについてフックのリファレンスでラッパーの形を確認してください。評価の位置は安定した部分で、ハンドラーのシグネチャはそれほどではありません。

信頼が育つにつれて締める​

権限モードは一度きりの決定ではありません。ストリーミングセッションでは、制限的に始めて、エージェントの最初の動きを確認した後に、Python の set_permission_mode() や TypeScript の setPermissionMode() で緩められます。新しいモードは、以降のすべてのツールリクエストに即座に適用されます。

default で開始し、アプローチを確認してから緩める

const q = query({
prompt: "Refactor the auth module",
options: {permissionMode: "default"}
});

// ...review the first turns, then:
await q.setPermissionMode("acceptEdits");

長時間の実行では逆の動きのほうが面白いです。新しい能力が不要なはずのフェーズにエージェントが入ったら、dontAsk に締めるのです。

ルールの出所​

許可、拒否、ask のルールは .claude/settings.json に宣言的に置くこともできます。SDK は project 設定ソースが有効なときにそれらを読み、デフォルトの query() オプションでは有効です。ただし settingSources を明示的に設定する場合は "project" を含めなければなりません。さもないと設定ファイルのルールは黙って適用されなくなります。 よくある事故です。再現性のために settingSources: ["user"] を追加した人が、プロジェクトの拒否リスト全体を静かに削除してしまいます。ファイルの優先順位は設定を参照してください。設定ファイル由来の許可ルールは CLAUDE_SDK_CAN_USE_TOOL_SHADOWED チェックからも見えないことを忘れないでください。

Check yourself

0/3
  1. allowedTools: ["Read"] と permissionMode: "bypassPermissions" を設定しました。エージェントは Bash を実行できますか。
  2. セキュリティチェックを、例外なくすべてのツール呼び出しで実行しなければなりません。どこに置くべきですか。
  3. Claude に Bash ツールが見えなくなるのは、どの拒否エントリですか。
Key takeaways
  • 6 ステップ、固定順序: フック、拒否ルール、ask ルール、権限モード、許可ルール、canUseTool。
  • 拒否はモードの上流にあるので絶対的。許可は下流にあり、事前承認しかできない。
  • allowedTools は許可リストではなく事前承認リスト。厳密にするには dontAsk と組み合わせる。
  • canUseTool 内のチェックは自動承認された呼び出しでは実行されない。無条件のチェックは PreToolUse フックに置く。
  • Edit(path) は Write と NotebookEdit を含む。Write(path) ルールは何にも一致しない。// はファイルシステムに、単一の / はルールの出所にアンカーされる。
  • サブエージェントは親のモードを継承するが、自身の定義の bypassPermissions の値は決して継承しない。
  • クリティカルパスの rm 削除は、許可ルールもフックの allow も通せないサーキットブレーカー。auto モードの Agent SDK セッションは、あなたを呼ばずにそれを拒否する。
  • settingSources を "project" なしで設定すると、.claude/settings.json のすべてのルールが黙って無効になる。

次へ​