Claude Codeの権限ルールは、書いたつもりで書けていない【5つの落とし穴】

Claude Code に何を許可し、何を止めるかは、allow / ask / deny の3つの権限ルールで決めます。認証情報を読ませない、rm を実行させない、といった線引きが settings.json に数行書くだけで引けます。
ところが公式ドキュメントを読み込むと、読ませたくないファイルや実行させたくないコマンドを、書いたルールが実際には止めていないパターンがいくつも出てきます。しかもどれも、警告が出るわけではありません。ルールは存在していて、狙ったものを止めていないだけです。ここでは、実務で踏みやすい5つを挙げます。2026年8月時点の公式ドキュメントに基づきます。
1. Read の deny は、スクリプト経由の読み取りを止めない
Read(./.env) を deny に入れると、Claude の組み込みファイルツールと、Bash のうち Claude Code が認識するファイルコマンド(cat、head、tail、sed など)からの読み取りが止まります。ただし止まるのは、Claude Code が認識する読み取り経路だけです。公式ドキュメントは、Read と Edit の deny ルールについてこう続けています。
これらは、Python または Node スクリプトがファイルを自分で開くような、ファイルを間接的に読み書きする任意のサブプロセスには適用されません。パスへのすべてのプロセスのアクセスをブロックする OS レベルの強制については、サンドボックスを有効にしてください。
出典:権限を設定する(Claude Code Docs)
引用中の「これら」は、Read と Edit の deny ルールを指します。つまり次の1行は、deny ルールを素通りします。
python -c "print(open('.env').read())"
Claude が意図して cat .env を打とうとすれば止まります。しかし、別のプロセスにファイルを読ませた場合、その読み取りは権限ルールの管轄外です。権限ルールが塞いでいるのは「Claude が取りにいく経路」であって、ファイルそのものではありません。プロセス単位で遮断したいなら、サンドボックスまで降りる必要があります。
2. パスのアンカーは、ルールを書いた場所で変わる
パスのアンカーは、誤設定に気づきにくい仕様です。Read(/secrets/**) と書けば、プロジェクトの secrets/ を守れそうに見えます。ところが先頭のスラッシュ1つは、ファイルシステムのルートを指していません。
| パターン | 意味 | 例と解決先 |
|---|---|---|
//path | ファイルシステムルートからの絶対パス | Read(//Users/alice/secrets/**) |
~/path | ホームディレクトリから | Read(~/Documents/*.pdf) |
/path | 設定ソースからの相対パス | プロジェクト設定なら <project root>/path |
path / ./path | 現在のディレクトリから | Read(*.env) → <cwd>/*.env |
/path は「そのルールを書いた設定ファイルの場所」を基準に解決されます。だから同じ1行でも、置いた場所によって守る対象が変わります。
ユーザー設定に書くと、ホーム配下を守るルールになる
出典:権限を設定する(Claude Code Docs)
Read(/secrets/**)のような deny ルールをユーザー設定で記述すると、プロジェクト内の secrets ディレクトリではなく、~/.claude/secrets/**をブロックします。すべてのプロジェクト内に適用されるユーザー設定でルールを記述するには、//絶対パスまたは~/ホーム相対パスを使用してください。
~/.claude/secrets/** というディレクトリは、たいていの人の環境に存在しません。設定ファイルには、存在しない場所を守るルールが1行増えるだけです。エラーも警告も出ません。
守りたい対象ごとに書き方を変える
書き分けは次のようになります。
| やりたいこと | 書き方 |
|---|---|
プロジェクトの secrets/ を守る | プロジェクト設定(.claude/settings.json)に Read(/secrets/**) |
どのプロジェクトでも .env を守る | ユーザー設定に Read(//**/.env)。/ ひとつでは届かない |
| ホーム配下の鍵を守る | Read(~/.ssh/**) |
Windows では、パスは照合の前に POSIX 形式へ正規化されます。C:\Users\alice は /c/Users/alice になるので、そのドライブ上の .env を止めるなら Read(//c/**/.env) と書きます。すべてのドライブ上の .env を止めるなら Read(//**/.env) と書きます。
ベアファイル名は gitignore と同じ意味になり、深さを問わずマッチします。Read(.env) と Read(**/.env) は同じで、どちらも現在のディレクトリ以下のすべての .env を止めます。ただし親ディレクトリや別プロジェクトの .env は対象外です。ファイルシステム全体で止めたいなら Read(//**/.env) と書きます。
3. allow ルールは複合コマンドを通さない。ただしラッパーは剥がされる
複合コマンドの扱いは、安全側に倒れています。Bash(safe-cmd *) という allow ルールがあっても、safe-cmd && rm -rf build は通りません。Claude Code はシェルの区切り文字(&&、||、;、|、|&、&、改行)を認識し、サブコマンドごとに独立して照合します。すべてのサブコマンドがルールに一致しない限り、承認は求められます。
剥がされるラッパーは5つだけ
一方で、Claude Code はマッチングの前に、決まったプロセスラッパーを剥がします。その結果、allow ルールが想定より広く適用されることがあります。
Bash ルールをマッチさせる前に、Claude Code は固定されたプロセスラッパーセットをストリップするため、
出典:権限を設定する(Claude Code Docs)Bash(npm test *)のようなルールはtimeout 30 npm testもマッチさせます。認識されるラッパーはtimeout、time、nice、nohup、stdbufです。
剥がされるラッパーは限られていて、そこに入っていないコマンドが抜け道になります。
devbox run、direnv exec、mise exec、npx、docker exec はストリップされません。これらは引数をコマンドとして実行するので、Bash(devbox run *) というルールは run の後に続くもの全部にマッチします。公式ドキュメントは、これに devbox run rm -rf . が含まれるとはっきり書いています。
開発環境ランナーを許可するときは、ランナー名だけで許可しない。Bash(devbox run npm test) のように、内部コマンドまで含めたルールを1つずつ書きます。
1回の承認で、allow ルールは最大5つ増える
権限プロンプトで「はい、今後は聞かない」を選んだとき、Claude Code は複合コマンド全体を1つのルールとして保存しません。承認が必要だったサブコマンドごとに、個別のルールを保存します。たとえば git status && npm test を承認すると npm test のルールが残るので、以後は && の前が何であっても npm test は通ります。1回の承認で最大5つのルールが増えることがあります。
つまり settings.json には、自分で書いた覚えのない allow ルールが溜まっていきます。定期的に見直す前提で運用してください。
もう1つ、ワイルドカードの前のスペースにも意味があります。Bash(ls *) は ls -la にマッチしますが lsof にはマッチしません。スペースが単語境界を強制するためです。スペースのない Bash(ls*) は両方にマッチしてしまいます。
4. フックと権限ルールには、決まった優先順位がある
PreToolUse フックと権限ルールを併用したときの評価順は、次のとおりです。公式ドキュメントの記述を整理しました。
| 組み合わせ | 結果 |
|---|---|
| フックが exit 2 + allow ルールあり | ブロック。exit 2 は権限ルールが評価される前にツール呼び出しを止める |
フックが "allow" を返す + deny ルールあり | ブロック。フックの判断は deny ルールをバイパスしない |
| deny ルール + より具体的な allow ルール | ブロック。ルールの具体性は評価順序を変えない |
表の1行目の性質を使うと、実用的な構成が1つ組めます。Bash 全体を allow に入れて毎回の確認をなくし、止めたい少数のコマンドだけを PreToolUse フックで弾く構成です。フックのブロックは allow より強いので、この形が成立します。公式ドキュメントも同じ組み合わせを勧めています。
逆に言えば、フックを入れたからといって deny ルールを外してよいことにはなりません。フックと権限ルールは打ち消し合わず、どちらか一方が止めれば、その呼び出しは止まります。
5. ベアツール名の deny は、ツールごと消える
Bash と Bash(rm *) は、deny に書いたときの挙動が違います。
Deny ルールは、ツール名を指定するか、ツール内のパターンをスコープするかによって異なる動作をします。
出典:権限を設定する(Claude Code Docs)Bashのようなベアツール名は、ツールを Claude のコンテキストから完全に削除するため、Claude はそれを見ることはありません。Bash(rm *)のようなスコープ付きルールは、ツールを利用可能なままにし、Claude が試みたときにマッチする呼び出しをブロックします。
前者はツールの存在ごと隠すので、Claude は「Bash を使う」という選択肢を検討しません。後者はツールを見せたうえで、該当する呼び出しだけを弾きます。
ベアツール名とスコープ付きのどちらを使うかは、目的で決まります。そもそもそのツールを使わせたくないならベアツール名、使わせたうえで一部だけ止めたいならスコープ付きです。コンテキストの節約という副次的な効果もあるので、MCPサーバーをまとめて無効にしたいときは mcp__* のようなベアネームの glob が使えます。
書いたルールが意図どおりか、先に確かめる
5つとも、症状が「何も起きない」なので、ルールを書いた側からは気づけません。設定を書いたその場で確かめる手段を用意しておきます。
セッション内で /permissions を実行すると、いま適用されているすべてのルールと、それがどの settings.json 由来かが一覧で出ます。ルールが意図した場所を守っているかは、書いた直後に /permissions で確認するのが確実です。とくにパスのアンカー(落とし穴2)は、この画面で解決先を確認しないと、意図したディレクトリを守れているか判断できません。
書き方をあらかじめ決めておくと、事故が減ります。ユーザー設定に置くパスは // または ~/ で始め、開発環境ランナーを許可するときは内部コマンドまで指定します。認証情報のように絶対に読ませたくないものは、権限ルールだけに頼らず、サンドボックスでも保護します。
参考文献・一次資料
- 権限を設定する(パターン構文、パスのアンカー、複合コマンド、プロセスラッパー、フックとの優先順位)
- Hooks リファレンス(終了コードの挙動と matcher)
- サンドボックス(OSレベルのファイルシステムとネットワーク分離)