Dev

Claude Code のパーミッションルール: 3 つのリリースで塞がれた 10 の抜け道

10 件の修正のうち半分は、ドキュメントがすでに認めている限界ではなく、ドキュメントが保証している内容に関わるものです。さらに 2 件は失敗時に素通りさせていた部品です。そして PreToolUse フックのスクリプトがクラッシュしたりハングしたりした場合に素通りするのは、今もドキュメント上の仕様のままです。

3 日間に出た 3 つの Claude Code リリース (v2.1.287〜v2.1.289) には、同じ形をした変更履歴が 10 行あります。deny ルール、ask ルール、フック、あるいは組織のパーミッション上限が何かを止めるはずだったのに、止めなかったというものです。1 行ずつ読めば定例のメンテナンスに見えます。まとめて読むと、この層がどこで壊れるかが見えてきます。そしてそれは、ドキュメントが注意を促している場所ではありません。

ドキュメントは Bash ルールの正体について率直です。ルールは Claude が書くコマンドのテキストにマッチするので、次のようになります。

text
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 ルールが、値が展開される環境変数プレフィックスの後ろにあるコマンドを見落としていたこと、そしてコマンドの前に単独の変数代入があるとルールが丸ごとスキップされていたことです。変更履歴自身の例はこれです。

bash
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 モードでも次が成り立つとあります。

text
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 つあったからです。パーミッションモードのページには、どのモードでも自動承認されないものが列挙されていて、その中にこれがあります。

text
rm and rmdir removals targeting a critical path, which no allow rule
or PreToolUse hook "allow" approves

v2.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 件目があります。

text
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 はブロックしないエラーで、処理はそのまま進みます。起動できないスクリプトも同じ扱いです。

text
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 以外のあらゆる終了を捕まえられます。

bash
#!/bin/bash
set -euo pipefail
trap 'rc=$?; [ "$rc" -eq 0 ] || exit 2' EXIT

bash 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 を本当に守るのは、それを取り戻すために必要なものです。

ディスカッション

コメント欄はありません。議論は X で行っています。

Max Nardit

Max Nardit

@mnardit

ほかの記事

AI 文章の電子透かし: OpenAI の textGrain、Claude の SynthID 方式の透かし、そして誰が確かめられるのか

文章の透かしの検出結果から何が言えるのか、どれだけの編集に耐えるのか、そして現時点で公開されている誤り率がなぜすべて鍵を持つベンダー自身の測定なのか。そのうえで、採点する人、書く人、これらのモデルの上で開発する人にとって何を意味するのかを考えます。

Claude Cowork がクラウドへ移行: サンドボックスはノート PC を離れ、フォルダの許可はそのまま

Anthropic 自身の安全性に関するページが一文で書いています。分離が制限するのは Claude のコードがどこで動くかであって、Claude が何を読み、何をするかではありません。クラウド移行は前者を移し、後者はデスクトップアプリにつないだままにしました。そのアプリが今、見ていない間も動き続けるセッションと手元のマシンをつなぐ経路になっています。