Claude CodeのBash権限ルールは複合コマンドをどう評価するか
&&やパイプで繋いだBashコマンドは、Claude Codeの権限ルールがサブコマンドごとに個別評価します。1本のルールで防いだつもりが漏れる典型パターンをまとめます。
Claude Codeの権限ルールは、&&・||・;・|・|&・&・改行のいずれかで繋がれた複合コマンドを、1本の文字列としてではなくサブコマンドごとに分解して評価します。Bash(safe-cmd *)というallowルールがあっても、safe-cmd && other-cmdはother-cmd側の評価が別途必要です。そのルールだけでは通りません。逆に言えば、複合コマンドの一部だけを狙ったdenyルールも、他のサブコマンドには及びません。
サブコマンドは1つずつ独立して判定される
複合コマンドの権限判定は「文字列全体が既知のパターンに一致するか」ではなく、「区切られた各サブコマンドがそれぞれのルールに一致するか」で決まります。認識される区切り演算子は&&・||・;・|・|&・&、そして改行の7種類です。
このため、git status && npm testのようなコマンドを実行させたい場合、Bash(git status *)とBash(npm test *)の両方が必要です。片方のallowルールしかなければ、もう一方のサブコマンドで確認プロンプトが立ちます。denyルール側も同じ仕組みで動くため、Bash(rm *)をdenyに置いても、ls && rm -rf tmpのls部分はブロックされません(rm部分だけが止まります)。「1つのBashルールで危険な操作を全部防げている」という思い込みは、複合コマンドを組み合わせた瞬間に崩れます。
「Yes, and don't ask again」は複合コマンドをどう保存するか
確認プロンプトで複合コマンドを「Yes, and don't ask again」で承認しても、Claude Codeは複合コマンド全体を1本のルールにはしません。保存されるのは承認が必要だったサブコマンドごとの個別ルールです。たとえばgit status && npm testを承認すると、保存されるのはnpm test用のルールだけです(git statusは元々読み取り専用で確認不要なため)。この結果、以降はnpm testが単独で実行されても、他のコマンドの後ろに続いても、そのルールで認識されます。
cdでサブディレクトリへ移動するサブコマンドは、そのパス用のReadルールとして別途保存されます。1回の複合コマンド承認で保存されるルールは最大5件までです。
ラッパーコマンドは除去されるものとされないものがある
権限ルールとの照合の前に、Claude Codeは固定リストのラッパーを取り除いてから中身のコマンドをルールに当てます。除去されるのはtimeout・time・nice・nohup・stdbuf、シェル組み込みのcommand・builtin、zshのnoglobです。Bash(npm test *)というルールは、これらを介したtimeout 30 npm testにもそのまま一致します。
先頭に付く既知の安全な環境変数代入も同様に除去されるため、Bash(npm test *)はNODE_ENV=test npm testにも一致します。ただしallowルールは既知の変数以外の代入をまたいでは一致しません。denyルール・askルールは逆にどんな代入も素通りして一致するため、Bash(rm *)をdenyに置いておけばFOO=bar rm -rf tmp/も止まります。フラグなしのxargsも除去対象で、Bash(grep *)はxargs grep patternに一致しますが、xargs -n1 grep patternのようにフラグが付くとxargsコマンドそのものとして扱われ、内側のコマンド用ルールでは対応できません。
command・builtinは無条件に除去されるわけではありません。公式ドキュメントは、問い合わせ形のcommand -v(コマンドの所在を調べるだけの用途)とzshのnocorrectについては除去対象に含まれないと明示しています。これらはラッパーとして剥がされず、command -vやnocorrectそのものをコマンドとしてルールに当てる必要があります。
| ラッパー | 除去されるか | 備考 |
|---|---|---|
timeout / time / nice / nohup / stdbuf | 除去されるかされる | 備考引数を実際のコマンドとして実行するため |
command / builtin(シェル組み込み)/ zshのnoglob | 除去されるかされる | 備考同上 |
| 既知の安全な環境変数の先頭代入 | 除去されるかされる | 備考未知の変数の代入は素通りしない(allowルールのみ) |
フラグなしのxargs | 除去されるかされる | 備考フラグ付きxargsは対象外 |
command -v(問い合わせ形) | 除去されるかされない | 備考中身を調べるだけの用途で、中身のコマンドとしては扱われない |
zshのnocorrect | 除去されるかされない | 備考除去リストの対象外 |
watch / setsid / ionice / flock | 除去されるかされない | 備考プレフィックスルールでは自動承認の対象にできず常に確認が必要 |
devbox run / npx / direnv exec / mise exec / docker exec | 除去されるかされない | 備考開発環境ランナーはリストに含まれない |
このラッパーリストは固定で、設定で追加や変更はできません。
環境ランナーとexecラッパーで見落としやすい抜け穴
devbox run・direnv exec・mise exec・npx・docker execのような開発環境ランナーは、上記の除去対象リストに入っていません。これらは引数をそのままコマンドとして実行する性質を持つため、Bash(devbox run *)というルールを書くと、runの後ろに続く任意のコマンド(devbox run rm -rf .を含む)がそのルールで通ってしまいます。環境ランナー越しに特定のコマンドだけを許可したいなら、ランナーと中身のコマンドをセットにした具体的なルール(Bash(devbox run npm test)など)を、許可したい中身のコマンドの数だけ書く必要があります。
watch・setsid・ionice・flockのようなexec系ラッパーは、Bash(watch *)のようなプレフィックスルールでは自動承認の対象にできません。Manual modeでは常に確認プロンプトが立ちます。同じ理由で、-execや-deleteを伴うfindもBash(find *)では対応できません。これらを承認したい場合は、コマンド全文に対する完全一致ルールを個別に書きます。
リダイレクト先は複合コマンドの評価とは別扱い
>・>>・2>のような出力リダイレクトは、複合コマンドの区切り演算子ではなく、リダイレクト先へのファイル書き込みとして別途チェックされます。Bash(git commit *)というルールはコマンド自体を許可するだけで、リダイレクト先までは許可しません。/dev/null宛てのリダイレクトはチェック対象外ですが、~から始まるパスやワイルドカードを含むパスへのリダイレクトは承認が必要です。
読み取り専用コマンドを含む複合コマンドの扱い
ls・cat・grepのような組み込みの読み取り専用コマンド同士を繋いだ複合コマンドは、各サブコマンドが単独で読み取り専用と判定できる限り確認なしで実行されます。ただしcdとgitを組み合わせた場合だけは例外で、cdが作業ディレクトリと異なる場所へ移動するときは確認が必要です(移動先のディレクトリのgit hookが実行される可能性があるため)。読み取り専用コマンドがどの条件で自動承認され、どこで例外的にプロンプトが立つかはClaude Codeで読み取り専用コマンドが自動承認される条件にまとめています。
引数を制約するBashパターンは壊れやすい
公式ドキュメントは、コマンドの引数を制約するBashパターンについて警告を出しています。たとえばBash(curl http://github.com/ *)のような「特定URL宛てのcurlだけ許可する」ルールは、一見安全に見えても実際には次のような形で簡単に外れます。
- オプションの前置:
curl -X GET http://github.com/のようにオプションを挟むと、パターンの先頭一致から外れます - プロトコルの差:
https://github.com/のようにhttpをhttpsに変えるだけで一致しなくなります - リダイレクト:
curl -L http://bit.ly/xyzのような短縮URL経由のリダイレクトは、最終的な接続先がパターンの外にあっても止められません - 変数展開:
URL=http://github.com && curl $URLのように変数へ入れてから展開すると、ルールの文字列一致がそもそも成立しません - 余分な空白: 引数の間に余分なスペースを挟むだけでも一致しないケースがあります
こうした理由から、公式は引数を制約するBashパターンそのものへの依存を推奨していません。代わりの手段として、次の3案が挙げられています。
curl・wgetをdenyに置き、代わりにWebFetchツールをdomain:指定で使う。ネットワークアクセスをBashの外に出すことで、引数パターンの穴を構造的になくします- PreToolUseフックでURLを検証する。フック側でリクエスト先を実際にパースしてチェックすれば、文字列パターンの揺れに引きずられません
- CLAUDE.mdへの記述だけに頼らない。CLAUDE.mdの指示は境界を強制する仕組みではないため、上記の権限ルールやフックと併用します
よくある質問
複合コマンド全体を1本のルールとして保存する方法はありますか
Bash(git status && npm test)のように複合コマンドの全文をそのままルールとして書けば、その完全一致に限っては1本のルールで通ります。ただしワイルドカードを使った部分一致ルールは各サブコマンドを個別に見るため、複合コマンドの組み合わせが変わるたびに新しいルールが必要になります。運用上は、サブコマンドごとにルールを用意する方が汎用的です。
PowerShellでも同じように複合コマンドを分解しますか
分解します。Claude CodeはPowerShellのASTを解析し、複合コマンド内の各コマンドを個別にチェックします。パイプ演算子|と文区切りの;、PowerShell 7以降ではチェーン演算子&&・||も複合コマンドを分割する対象です。複合コマンドが許可されるには、すべてのサブコマンドがルールに一致する必要があります。
PreToolUseフックで複合コマンドの穴を塞げますか
塞げます。フックの判定は権限ルールを迂回しません。deny・askルールはフックが何を返しても評価されるため、一致するdenyルールがあればフックの判定に関わらずブロックされます。運用としては、Bashをallowリストに丸ごと入れて確認プロンプト自体を止めたうえで、ブロックしたい特定のコマンドだけをPreToolUseフックで拒否する構成が使えます。
複合コマンドのルール一致は/permissionsから確認できますか
確認できます。/permissionsは現在有効なallow・ask・denyルールをスコープごとに一覧するダイアログで、複合コマンドの承認時に保存された個別ルールもここに並びます。ルールの追加・削除の操作方法は/permissionsコマンドで権限ルールとauto mode拒否を管理するで扱っています。
まとめ
Claude Codeの複合コマンド評価は「文字列全体を1つのパターンとして見る」のではなく、「区切り演算子で分割したサブコマンドをそれぞれ独立に判定する」仕組みです。1本のBashルールで複合コマンド全体をカバーしたつもりでも、&&の後ろや|の先で別のサブコマンドが評価漏れになっていないかは、ルールを書くたびに確認する価値があります。とくにdevbox runやnpxのような環境ランナーはラッパー除去リストに含まれないため、ランナー越しのコマンドを絞り込みたいときはランナーと中身をセットにした具体的なルールを書きます。繰り返し承認しているサブコマンドの組み合わせがあるなら、/fewer-permission-promptsで許可プロンプトを自動整理するで会話履歴からallowリストへの反映を自動化できます。