1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code権限ルール、pathが効くのはRead/Editだけ

1
Posted at

はじめに

.claude/settings.json に "deny": ["Write(.env)"] と書いて、.env への書き込みを塞いだつもりになっていないでしょうか。Claude Code はこのルールを読み込みますが、ファイルの権限チェックでは一度も参照しません。書いた本人だけが「守られている」と思い込む状態が、設定ファイルの中に静かに残ります。

Claude Code v2.1.210(2026年7月)で、こうした書き方に起動時警告が付きました。では現行の v2.1.300 で、警告はどこまで拾ってくれるのか。警告されない書き方は本当に安全なのか。この記事では v2.1.300 で起動時警告と実際のツール呼び出しの両方を確かめ、Grep(path) という警告されない抜け穴が今も残っていることを示します。

対象読者は、.claude/settings.json で permissions.allow / permissions.deny を書いている方、Claude Code をチーム・CI・スケジュール実行で運用している方です。

この記事で学べること

  • 権限ルールでファイルパスを指定して効くのは Read(path) と Edit(path) だけであること
  • v2.1.300 で起動時警告が出る書き方と、出ないのに効かない書き方(Grep(path))
  • Grep(path) の deny が実際の Grep 呼び出しを止めなかった実測と、Read(path) なら止まること
  • 自分の settings.json を棚卸しするコマンド

前提環境

  • Claude Code 2.1.300(claude --version で確認・2026-10-15)
  • 実行環境: Claude Code on the web のクラウドセッション(Linux)

TL;DR

  • ファイル権限チェックが参照するのは Edit(path) と Read(path) のルールだけです。Edit は書き込み系ツール全体、Read は読み取り系ツール全体をカバーします
  • Write(path) / NotebookEdit(path) / Glob(path) / MultiEdit(path) は v2.1.300 で起動時警告が出ます
  • Grep(path) は警告が出ず、しかも効きません。Grep(./secret/**) を deny にしても Grep は secret/ を検索できました
  • 棚卸しは grep -nE '"(Write|NotebookEdit|Glob|Grep|MultiEdit)\(' .claude/settings.json で足ります

権限ルールの2カテゴリ構成

公式ドキュメント(Configure permissions)は、ファイル系のルールを次のように定めています。

Claude Code checks file permissions against Edit(path) and Read(path) rules only. If you write a path rule for Write, NotebookEdit, Glob, or the legacy MultiEdit tool instead, Claude Code accepts the rule but never consults it, and warns at startup

ツール名ごとに path ルールがあるのではなく、読み取り系と書き込み系の2カテゴリにまとめて判定する設計です。

同じページには、パスなしのツール名だけのルール(たとえば deny の Write)は警告対象外で、ツール単位で常にマッチするとも書かれています。効かないのは「書き込み系・読み取り系のツール名に path を付けた形」だけです。

なお v2.1.210 時点のドキュメントと比べると、MultiEdit が警告対象として明記されたほか、Read の deny が Edit・Write ツールによる同じパスへの書き込みも止めるという記述が加わっています(書き込みへの適用は v2.1.228 以降)。

v2.1.300 での起動時警告

ステップ1: バージョン確認

$ claude --version
2.1.300 (Claude Code)

ステップ2: 8種類の allow ルールを読み込ませる

scratchpad の一時ディレクトリに、ファイル系と非ファイル系を混ぜた settings.json を用意しました。

{
  "permissions": {
    "allow": [
      "Write(/tmp/scratch/**)",
      "Edit(/tmp/scratch/**)",
      "Read(/tmp/scratch/**)",
      "Glob(/tmp/scratch/**)",
      "Grep(/tmp/scratch/**)",
      "NotebookEdit(/tmp/scratch/**)",
      "Bash(echo *)",
      "WebFetch(domain:example.com)"
    ]
  }
}

--bare(hooks や CLAUDE.md の自動探索を省く軽量起動)で読み込ませ、標準エラーに出る警告だけを見ます。--bare では認証情報を読まないため、本体は Authentication error で終了しますが、警告は設定の読み込み時点で出力されます。

$ claude -p "2+2" --bare --settings ./settings.json --add-dir .
Permission allow rule (settings.json): Write(/tmp/scratch/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(/tmp/scratch/**) instead (Edit rules cover all file-editing tools).
Permission allow rule (settings.json): Glob(/tmp/scratch/**) is not matched by file permission checks — only Read(path) rules are. Use Read(/tmp/scratch/**) instead (Read rules cover all file-reading tools).
Permission allow rule (settings.json): NotebookEdit(/tmp/scratch/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(/tmp/scratch/**) instead (Edit rules cover all file-editing tools).

警告は3行で、Grep(/tmp/scratch/**) には何も出ませんでした。この結果は、2026年7月に v2.1.211 で同じ設定を試したときの記録と一致します。v2.1.210 から 2.1.300 までの間、Grep(path) は警告リストに加わっていません。

ステップ3: deny 側と、存在しないツール名

deny でも同じ挙動か、存在しないツール名を書いたらどうなるかを確かめました。

{"permissions":{"deny":["Write(./secret/**)","Grep(./secret/**)","Glob(./secret/**)","NotebookEdit(./secret/**)","Edit(./secret/**)","Read(./secret/**)","MultiEdit(./secret/**)","LS(./secret/**)"]}}
Permission deny rule "MultiEdit(./secret/**)" matches no known tool — check for typos.
Permission deny rule "LS(./secret/**)" matches no known tool — check for typos.
Permission deny rule (deny.json): Write(./secret/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(./secret/**) instead (Edit rules cover all file-editing tools).
Permission deny rule (deny.json): Glob(./secret/**) is not matched by file permission checks — only Read(path) rules are. Use Read(./secret/**) instead (Read rules cover all file-reading tools).
Permission deny rule (deny.json): NotebookEdit(./secret/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(./secret/**) instead (Edit rules cover all file-editing tools).
Permission deny rule (deny.json): MultiEdit(./secret/**) is not matched by file permission checks — only Edit(path) rules are. Use Edit(./secret/**) instead (Edit rules cover all file-editing tools).

MultiEdit と LS には「既知のツールに一致しない」というタイプミス警告も出ます。ここでも Grep(./secret/**) だけは何も言われません。

パラメータ指定の形 Grep(path:./secret/**) も試すと、こちらは別の警告で弾かれました。

Permission deny rule "Grep(path:./secret/**)" targets path as a raw string and will not match.

警告の有無の一覧

ルールの書き方 v2.1.300 の起動時警告 実際の扱い
Read(path) なし 正しい書き方。Read・Grep・Glob をまとめてカバー
Edit(path) なし 正しい書き方。Edit・Write・NotebookEdit をまとめてカバー
Write(path) あり 参照されない。Edit(path) を使う
NotebookEdit(path) あり 参照されない。Edit(path) を使う
MultiEdit(path) あり(タイプミス警告も併発) 参照されない。Edit(path) を使う
Glob(path) あり 参照されない。Read(path) を使う
Grep(path) なし 参照されない(次節で実測)。Read(path) を使う
Bash(cmd) / WebFetch(domain:...) なし ファイル系ではない別カテゴリ。正常

警告が出ない行には、正しい書き方と Grep(path) が混在しています。警告の有無だけでは両者を区別できません。

警告されない Grep(path) は本当に効かないのか

警告が出ないなら、実は効いているのではないか。これは読者が当然抱く疑問です。そこで認証ありの claude -p で、実際に Grep ツールを呼ばせて確かめました。proj/secret/a.txt に TOKEN=abc123 を置き、プロジェクトの .claude/settings.json に deny を1行だけ書きます。

$ claude -p "Use the Grep tool to search for the pattern TOKEN in the secret directory. Report exactly what the tool returned (including any permission denial). Do not use any other tool." \
    --allowedTools Grep --output-format json

Grep(./secret/**) を deny にした場合、Grep はそのまま中身を返しました。JSON 出力の permission_denials も空配列です。

The Grep tool returned one match and no permission denial:

secret/a.txt:1:TOKEN=abc123

同じ条件で deny を Read(./secret/**) に替えると、Grep は拒否されました。

Permission to read .../perm486/proj/secret has been denied.

書き込み側も同じ構図です。Write ツールで secret/b.txt を作らせると、結果は deny ルールによって次のように分かれました。

deny ルール Write ツールの結果
Write(./secret/**) 作成成功(拒否なし)
Edit(./secret/**) File is in a directory that is denied by your permission settings.
Read(./secret/**) File is covered by a Read deny rule in your permission settings and cannot be written.

Read の deny が書き込みまで止める挙動は、前節で触れたドキュメントの記述どおりです。秘密ファイルを守る目的なら、Read(path) の deny 1行で読み取りと Edit・Write による書き込みの両方を塞げます。ただしドキュメントは NotebookEdit が対象外だと明記しているため、変更を一切許さないパスには Edit(path) の deny も併記します。

2.1.210 以降の changelog に見る関連修正

公式 CHANGELOG.md を 2.1.210 から 2.1.300 まで見ると、権限ルールの「書いたのに効かない」を減らす修正が続いています。

  • 2.1.210: Write(path) / NotebookEdit(path) / Glob(path) への起動時警告を追加
  • 2.1.260: 閉じ括弧の後ろに文字があるルール(例 Bash(ls) x)を、黙って無視せず不正な設定として報告するよう変更。パスに括弧を含む Edit/Write/Read ルールが無効扱いされていた問題も修正
  • 2.1.275: /update-config が Edit(path) ではなく Write(path) ルールを書き込んでいた不具合を修正

最後の項目は、Claude Code 自身の設定支援コマンドでさえ Write(path) を書いていたことを示しています。2.1.275 より前に /update-config で追加した deny ルールがあるなら、一度確認する価値があります。一方で、Grep(path) を警告対象に加える変更は、この範囲の changelog には見当たりませんでした。

自分の設定を棚卸しする

本記事を書いているリポジトリ自身の .claude/settings.json を、2026-10-15 時点で確認しました。

$ grep -nE '"(Write|NotebookEdit|Glob|Grep)\(' .claude/settings.json
38:      "Write(.claude/settings.local.json)",
40:      "Write(.env)",
42:      "Write(**/*.pem)",
...
64:      "Write(**/id_ed25519)",

permissions.deny に Write(...) が14行ありました。この permissions を v2.1.300 に読み込ませると、14行すべてに「is not matched by file permission checks」の警告が出ます。

$ claude -p "hi" --bare --settings ./repo-perms.json < /dev/null 2> err.txt
$ grep -c 'is not matched' err.txt
14

幸い、14行それぞれに対応する Edit(...) が隣の行に併記されており、同じパスの Read(...) deny もあるため、保護漏れはありません。それでも Write(...) の14行は何も守っていない死んだルールです。正しい形と間違った形が並んでいる設定は、後から読む人に「Write 側も必要なのか」という誤解を残します。

棚卸しの手順は次の2つです。

  1. grep -nE '"(Write|NotebookEdit|Glob|Grep|MultiEdit)\(' .claude/settings.json で該当行を探す。Grep( は警告が出ないため、grep で拾うしかありません
  2. 見つかった行を、書き込み系なら Edit(path)、読み取り系なら Read(path) に置き換える。対応する正しい行がすでにあるなら、間違った行は削除する

まとめ

冒頭の "deny": ["Write(.env)"] は、v2.1.300 でも .env を守りません。起動時に警告が出るようになった分だけ気づきやすくはなりましたが、Grep(path) は警告も出ないまま効かない、という状態が v2.1.210 から変わっていません。実測では Grep(./secret/**) の deny は Grep を止めず、Read(./secret/**) なら止まりました。

  • ファイル権限チェックが参照するのは Read(path) と Edit(path) だけ
  • 警告が出ないことは、そのルールが効いている証拠にならない
  • 秘密ファイルは Read(path) と Edit(path) の deny で守り、Write( や Grep( の path ルールは grep で探して置き換える

次のステップとして、手元の .claude/settings.json と ~/.claude/settings.json に上の grep を一度かけてみてください。

参考リンク

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?