Claude Code のパーミッションルール: 3 つのリリースで塞がれた 10 の抜け道
10 件の修正のうち半分は、ドキュメントがすでに認めている限界ではなく、ドキュメントが保証している内容に関わるものです。さらに 2 件は失敗時に素通りさせていた部品です。そして PreToolUse フックのスクリプトがクラッシュしたりハングしたりした場合に素通りするのは、今もドキュメント上の仕様のままです。
3 日間に出た 3 つの Claude Code リリース (v2.1.287〜v2.1.289) には、同じ形をした変更履歴が 10 行あります。deny ルール、ask ルール、フック、あるいは組織のパーミッション上限が何かを止めるはずだったのに、止めなかったというものです。1 行ずつ読めば定例のメンテナンスに見えます。まとめて読むと、この層がどこで壊れるかが見えてきます。そしてそれは、ドキュメントが注意を促している場所ではありません。
ドキュメントは Bash ルールの正体について率直です。ルールは Claude が書くコマンドのテキストにマッチするので、次のようになります。
a deny or ask rule covers the invocation Claude usually produces and
isn't a security boundary around the program.同じページには、Bash(rm *) の deny ルールでは止まらない形として /bin/rm -rf build/ と bash -c 'rm -rf build/' が挙げられています。10 件の修正のどれも、この注意書きに関するものではありません。5 件はドキュメントが保証として書いている一文を破っており、2 件は失敗時に素通りさせていた部品です。残りの 3 件は周縁にあります。ドキュメント自身がベストエフォートと呼ぶ経路、組織が管理するテキストを上書きするユーザーのプラグイン、そしてシェルの算術を見落としたチェックです。以下はすべて Anthropic の変更履歴にもとづいています。バグの再現はしていません。10 件とも 2.1.289 の時点で修正済みです。
Claude Code の deny ルールはサンドボックス auto-allow 下で効いていたか
変数代入が先に来た場合は効いていませんでした。v2.1.289 に修正が 2 件あります。Bash の deny ルールと ask ルールが、値が展開される環境変数プレフィックスの後ろにあるコマンドを見落としていたこと、そしてコマンドの前に単独の変数代入があるとルールが丸ごとスキップされていたことです。変更履歴自身の例はこれです。
TZ="$HOME" rm -rf buildどちらもドキュメントと真っ向から矛盾します。パーミッションのページには、deny ルールは「matches past any leading assignment, so Bash(rm *) in deny still matches FOO=bar rm -rf tmp/」とあり、サンドボックスのページには auto-allow モードでも次が成り立つとあります。
Explicit deny rules are always respectedドキュメントの例はリテラルの値を使っていて、1 件目のバグはシェルが展開する値が必要でした。2 件目は位置づけが難しく、変更履歴には「bare variable assignment」としか書かれていません。これがドキュメントにある FOO=bar の形を指すなら、auto-allow 下で失敗していたのはドキュメント自身の例だったことになります。
ここで飛びつきたくなる慰めがあります。auto-allow はサンドボックス内で動くコマンドにしか適用されないのだから、サンドボックスは守られていた、というものです。デフォルトのポリシーでは、これはあまり助けになりません。サンドボックスが制限するのはコマンドの書き込み先で、作業ディレクトリはデフォルトで書き込み可能です。ありふれた build フォルダは無防備です。この構成では、コマンドを名指しできていたのは deny ルールだけでした。
v2.1.288 の 3 件目の修正もこの 2 件と並びます。シェルが算術として評価する値を持つ BASHPID への代入について、以前は黙って許可していたのが、パーミッションチェックで確認を求めるようになりました。公開されている情報はこの 1 行だけなので、仕組みを推測することはしません。見立てとしては、これはドキュメントがすでに扱っているケースの仲間です。Manual モードでは、PATH や IFS のような特殊なシェル変数への書き込みは、それ以外は読み取りだけのコマンドの中でも確認を求めます。コマンドのテキストからは見えない形でシェルが作用する値であり、BASHPID の算術もその一つに見えます。
Claude Code の bypassPermissions モードで危険な rm が確認なしに実行されたのはなぜか
bypass モードでも残る数少ない安全装置の一つに、穴が 2 つあったからです。パーミッションモードのページには、どのモードでも自動承認されないものが列挙されていて、その中にこれがあります。
rm and rmdir removals targeting a critical path, which no allow rule
or PreToolUse hook "allow" approvesv2.1.288 は、/ やホームディレクトリを対象にした危険な rm が bash -c や sh -c のスクリプト内にあると、bypassPermissions モードやシェルの allow ルールの下で確認なしに実行されていた問題を修正しました (#96300)。v2.1.287 は、同じ rm が ~ やワイルドカードのパスへの出力リダイレクトを伴うと、常に確認するという保護を失っていた問題を修正しました。
この最後の砦は、ユーザーが書くたいていのルールより重要です。確認を切った人に残された数少ないチェックの一つだからです。コマンドごとの確認が運用者を bypass フラグへと追いやると以前論じました。その道を選び、deny ルールを書かなかった人は、まさにこのチェックに頼っていました。コマンドを 2 つ目のシェルで包むか、リダイレクトを足すだけで、それを乗り越えられたわけです。
Claude Code の Read deny ルールは IDE から渡されたファイルにも効いていたか
ファイルがシンボリックリンク経由で来た場合は効いていませんでした。v2.1.289 は、IDE 上でシンボリックリンクを経由して @ メンション、変更、選択されたファイルに Read の deny ルールが適用されていなかった問題を修正しました。これは約束違反ではありません。ドキュメントは deny ルールが「when either the requested path or the file it resolves to matches」で適用されると書く一方で、@ による言及や接続された IDE が共有するコンテキストに Read ルールを適用するのは「a best-effort attempt」だとも書いています。IDE はファイルの内容がモデルに届く第 2 の経路であり、ルールがその経路をゆるくしか追わないことについて、ドキュメントは正直でした。
これもまたカバー範囲の問題です。チェックが縛れるのは自分がカバーする経路だけであり、モデルにコンテキストを渡す新しい方法は、そのたびに新しい経路になります。
Claude Code の管理対象 deny ルールは複合コマンドで mod に対して効いていたか
入れ子になった部分については効いていませんでした。管理設定のあるマシンでは、deny ルールはデフォルトでユーザーがインストールした mod より優先されます。これは mod がフックの優先順位をどう変えるかについて唯一はっきりした答えでした。それとは別に、ドキュメントは deny ルールと ask ルールが「when any subcommand matches them, including a command nested inside a subshell, a command substitution, or a control-flow body」で適用されると書いています。v2.1.289 は、管理対象のマシンで、複合コマンドの入れ子部分にかかる deny ルールや ask ルールが mod の承認に対して効いていなかった問題を修正しました。2 つの文を並べると、このバグの deny 側は保証違反です。ask 側はそれほど明確ではありません。ドキュメントは mod が ask ルールを越えて承認できると書いているからです。それでも変更履歴は、管理対象のマシンではこれをバグとして扱っています。
Claude Code のパーミッションシステムはどこで失敗時に素通りさせていたか
シェルの構文解析とは無関係な 2 か所です。さらに、ブロックの失敗ではないものの同じリストに入る 3 件目があります。
v2.1.288 PreToolUse and PermissionRequest hooks were skipped when matching
them failed or the tool input could not be serialized to JSON.
The call is now blocked.
v2.1.287 Organization per-tool permission ceilings were silently dropped
for an MCP tool named __proto__.
v2.1.289 A user-installed plugin could rewrite the descriptions of an
organization-managed MCP server's sign-in tools.最後の 2 件について、変更履歴はこれ以上何も書いていません。__proto__ は通常の JavaScript オブジェクトで特殊なレガシー挙動を持つので、バグの種類を推測するには十分ですが、断定するには足りません。プラグインの項目は、呼び出しを通してしまったチェックではありません。組織が管理するテキストを、ユーザーレベルの部品が上書きしていたという話です。ツールの説明は、モデルがそのツールを呼ぶかどうか、どう呼ぶかを決めるときに読むものなので、これは重要です。
注意して読むべきはフックの修正です。文面から受ける印象よりカバー範囲が狭いからです。塞がれたのはマッチングとシリアライズの失敗です。フックのスクリプト自体が失敗した場合の挙動は変わっておらず、フックのリファレンスはその点をはっきり書いています。有効な JSON を伴わない終了コード 1 はブロックしないエラーで、処理はそのまま進みます。起動できないスクリプトも同じ扱いです。
For most hook events, the action proceeds. When you set up a policy
hook, watch for this notice on its first run: a mistyped path in
settings.json leaves the gate silently disabled.さらに PreToolUse では、タイムアウトしたコマンドフックは「doesn't block the tool call」とされています。それ単独でブロックするのは終了コード 2 だけです。ですから、フックをゲートにしているなら、その中のあらゆる失敗が終了コード 2 で終わるようにしてください。すぐ思いつく bash の定型、trap 'exit 2' ERR では足りません。set -E がないと関数内ではトラップが発火しませんし、set -u の下で未定義変数に触れると、トラップを発火させないまま 1 で終了します。EXIT トラップなら 0 以外のあらゆる終了を捕まえられます。
#!/bin/bash
set -euo pipefail
trap 'rc=$?; [ "$rc" -eq 0 ] || exit 2' EXITbash 5.2 で、トップレベルでのコマンド欠落、関数内でのコマンド欠落、未定義変数の 3 つを試しました。このヘッダーの下では 3 つとも終了コード 2 になり、正常な実行は従来どおり 0 で終わります。捕まえられるのは、スクリプト自身が処理しない失敗だけです。if の中、|| の後、コマンド置換の奥で起きた失敗は、引き続き自分で確認して終了コード 2 に変換する必要があります。信頼する前に、依存関係が欠けた状態と不正な入力でフックをテストしてください。起動しないフックやハングするフックは、スクリプト内の対策ではカバーできません。初回実行で hook error の通知に目を配り、フックを速く保ってください。
Claude Code の 10 件の修正は、安全層としての deny ルールについて何を語るか
ドキュメントの約束ではなく仕組みで切り分けると、10 件は 2 つのグループに分かれます。1 つは、チェックがシェルの意味論とぶつかる場所です。展開、代入、算術、入れ子のシェル、リダイレクト、複合コマンド内のサブコマンド。もう 1 つは、誰もチェックしていなかった経路に部品が出会う場所、あるいは部品が失敗時に素通りさせる場所です。IDE、マッチできなかったフック、オブジェクトのキー、管理対象のテキストを上書きするプラグイン。予想としては、1 つ目のグループはこれからも修正を生み続けます。その舞台はシェルの文法そのものであり、コマンドのテキストに対するチェックは、シェルが受け入れるあらゆる構文を先回りして想定しなければならないからです。
ですから deny ルールと ask ルールは、ドキュメントが求めるとおりに読むのがよいと思います。Claude がふだん書くコマンドに対する舵取りとしてです。それは役に立つ舵取りです。コマンドがどう書かれても守られなければならないルールは、テキストが関係ない場所に置くべきです。サンドボックス自体のファイルシステムやネットワークの制限、あるいはセッションに最初から渡さない認証情報です。そうした境界も漏れます。ただ、これまで書いてきたケースでは、漏れたのはエージェントが別の仕事のために渡されたサービスを通じてであり、コマンドの書き方を通じてではありませんでした。とはいえ、テキストでしか名指しできないルールもあり、rm -rf build はその一つです。そうしたルールは本質的に舵取りのままです。build を本当に守るのは、それを取り戻すために必要なものです。