Claude Codeで.envを読ませないRead denyの書き方と効く範囲
Claude Codeに.envを読ませない設定は、permissions.denyのReadルールで書きます。.env.*の例外、サブディレクトリへの効き方、Bashのcatに効く範囲と効かない範囲を解説します。
Claude Codeに.envを読ませないには、permissions.denyにRead(.env)のようなルールを書きます。.claudeignoreを置いても効果はありません。ルールはClaude Code本体が強制するため、CLAUDE.mdに「.envは読まない」と書くだけの運用とは強さが違います。
.envを読ませない最小の設定
プロジェクトの.claude/settings.jsonに次の3行を足すのが出発点です。
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(!.env.example)"
]
}
}1行目が.env本体、2行目が.env.localや.env.productionのような派生ファイル、3行目がテンプレートの.env.exampleだけを読める例外です。設定リファレンスにある例はRead(./.env)とRead(./.env.*)の組で、考え方は同じです。
/permissionsを開くと、ルールと、それを定義している設定ファイルの対応が一覧で見えます。
パターンの書き方で変わる効く場所
Readのパスはgitignoreと同じ書式です。同じ.envでも、書き方によって効く範囲が変わります。
| 書き方 | 効く範囲 |
|---|---|
Read(.env) / Read(**/.env) | 効く範囲カレントディレクトリ以下にあるすべての.env(両者は同じ意味) |
Read(//**/.env) | 効く範囲ファイルシステム上のすべての.env(ルート起点) |
Read(~/work/**/.env) | 効く範囲ホーム直下のwork配下にある.env |
裸のファイル名はgitignoreの規則どおり任意の深さにマッチします。モノレポのapps/api/.envもRead(.env)1行で塞がれる一方、親ディレクトリにある.envや別プロジェクトの.envは対象外です。起動ディレクトリより上にあるファイルまで塞ぎたいときは、//で始まる絶対パスで書きます。
先頭スラッシュの罠
Read(/secrets/**)のように単一のスラッシュで始めると、絶対パスではなく設定ファイルの置き場所を起点に解釈されます。プロジェクトの.claude/settings.jsonなら作業ディレクトリ起点ですが、ユーザー設定の~/.claude/settings.jsonに書くと~/.claude/secretsを指します。全プロジェクトに効くルールをユーザー設定へ置くなら、//の絶対パスか~/で始めます。Windowsではパスが/c/Users/...の形に正規化されるので、C:ドライブ全体の.envは//c/**/.envと書きます。
.env.exampleだけを読めるようにする
denyの!は、直前までのルールから対象を抜き出す否定として働きます。Read(.env.*)の後ろにRead(!.env.example)を置くと、.env.exampleだけが読めるようになります。順序が逆だと何も抜き出せません。
注意点が2つあります。否定が効くのは同じ設定ファイルのルールに対してだけで、プロジェクト設定のRead(!.env)が管理設定のdenyを打ち消すことはありません。また、ディレクトリごとdenyしたルール(Read(secrets/**))の中のファイルは、否定でも開けません。
止まるものと止まらないもの
Readのdenyは、Readツールだけでなく複数の経路に適用されます。範囲は次のとおりです。
Read denyが効く経路と効かない経路
効く
Readツール、Grep・Globのような読み取り系の組み込みツール、プロンプトの@file参照、接続したIDEが共有する選択範囲や開いているファイルです。Bashのcat・head・tail・sed・teeと、< .envのような入力リダイレクトの宛先も対象になります。
効かない
作業ディレクトリで走らせるgrep -r pattern .のように、ファイルを名指ししないコマンドです。PythonやNodeのスクリプトが自分で.envを開く経路にも効きません。
「Readのdenyはファイル系ツールだけを止め、Bashのcatは別」と説明する記事がありますが、現行のドキュメントではcat .envのようにファイル名を示したコマンドも止まる側に入っています。逆に、止まらない側に入っているのは、ファイル名を示さない読み取りとスクリプト経由の読み取りです。組み込みツールへの適用は「best-effort」(最善努力)とされている点も覚えておく必要があります。完全な遮断の保証ではありません。
Bashのルール自体にも限界があります。Bash(cat .env)のようにコマンド文字列で塞ぐ方式は、/bin/cat .envやsh -c '...'のような別の書き方を止められません。ファイルを塞ぎたいなら、コマンドではなくReadのパスで書くほうが筋の通った選択です。
書き込みも止まる — EditとWriteへの副作用
Readのdenyは、同じパスに対するEditツールとWriteツールも止めます。新規作成も含みます。.envをClaudeに編集させたい場面では、denyに引っかかって次のメッセージが出ます。
File is covered by a Read deny rule in your permission settings and cannot be edited.Writeの場合は末尾がand cannot be writtenになります。この挙動はEditでv2.1.208以降、Writeでv2.1.228以降です。エラーの切り分けは「Read deny rule」エラーの対処にまとめてあります。
NotebookEditだけはReadのdenyの対象外です。ノートブック形式の機密ファイルがあるなら、Edit(...)のdenyを別に足します。
シンボリックリンクとディレクトリ参照の扱い
.envが別の場所へのシンボリックリンクでも、denyは回避されません。ファイルパスがシンボリックリンクを通るとき、権限チェックは要求されたパスとリンク先の両方を見ます。denyは「どちらかがマッチすれば」適用されるので、config/.envから~/secrets/prod.envへリンクを張っていても、Read(.env)が効いている限り止まります。
ディレクトリの@参照にも効きます。@config/のようにディレクトリを指定したとき、Claudeが受け取る一覧からdenyにマッチしたファイル名とディレクトリ名は省かれます。ターミナルに貼り付けた画像のパスも、ルールにマッチすれば添付されません。この2つはv2.1.290以降の挙動です。
Writeにパスを書いても効かない
「書き込みも塞ぎたい」と考えてWrite(.env)を足しても、Claude Codeはそのルールを参照しません。パスつきのファイル権限ルールを見るのはEdit(path)とRead(path)だけで、Write・NotebookEdit・Globに書いたパスルールは受理されるものの無視され、起動時に警告が出ます(v2.1.210以降)。書き込みを塞ぐならEdit(.env)、読み取りの範囲を絞るならRead(...)と書きます。
実際に使う一式は、たとえば次の形になります。
{
"permissions": {
"deny": [
"Read(.env)",
"Read(.env.*)",
"Read(!.env.example)",
"Read(secrets/**)",
"Edit(.env)",
"Edit(.env.*)"
]
}
}Read(secrets/**)のような単一セグメントのディレクトリパターンは、denyでは任意の深さのsecretsにマッチします(allowでは直下だけ)。同じ書き方でも、ルールの種類で深さが変わる点に気をつけます。ReadのdenyはEditとWriteも止めるため、Edit(...)の行はNotebookEditまで塞ぎたいときの保険です。
.claudeignoreは読まれない
他のAIツールの感覚で.claudeignoreを置いても、Claude Codeは見ません。権限のドキュメントは、中身をReadのdenyルールに移すよう案内しています。旧来のignorePatterns設定も、現在のpermissions.denyに置き換わっています。
抜けを埋めるには — denyとサンドボックスの併用
.envをプロセスから隠したいなら、OS側の遮断を重ねます。denyルールが及ばない経路は、サンドボックスを有効にして補うのが案内されている方法です。permissions.denyのRead(...)のパスは、サンドボックスのdenyReadにも取り込まれます。両方を併用するなら、.envの書き方は1か所で済みます。
サンドボックス側のdenyReadを単独で使う設定はsandbox.filesystem.denyReadの解説にあります。作業ディレクトリの外を読ませたくないなら作業ディレクトリ外の読み取りを一括禁止する設定が別の手段です。
どの層で止めるか
| 守りたいもの | 向く手段 |
|---|---|
Claudeのツールが.envを開くこと | 向く手段permissions.denyのRead(.env) |
スクリプトやサブプロセスが.envを開くこと | 向く手段サンドボックスのdenyRead |
.envの値がBashの環境変数に混ざること | 向く手段別の問題(原因と修正を扱った記事) |
| チーム全員に強制すること | 向く手段管理設定のdeny(どのスコープのdenyも上書きできない) |
denyは評価の順序で最優先です。ルールはdeny、ask、allowの順に評価され、allowでdenyの例外は作れません。あるスコープでdenyしたパスは、別のスコープにallowを書いても開きません。
チームで揃える・worktreeで使う
個人の.claude/settings.local.jsonではなく、リポジトリにコミットする.claude/settings.jsonにdenyを書けば、クローンした全員に同じルールが届きます。プロジェクト設定のdenyをユーザー設定のallowが上書きすることはなく、逆にユーザー設定のdenyもプロジェクト設定のallowより強いため、メンバーが手元で緩める余地はallowの側にはありません。
組織として外せない形にしたいときは管理設定に書きます。管理設定のdenyは、コマンドライン引数の--allowedToolsでも、他のどの設定ファイルのallowでも打ち消せません。!の例外が同じ出どころのルールにしか効かない点は前述のとおりです。
worktreeでは、ローカル設定の/path型ルールが、そのセッションの作業ディレクトリ(worktree自身)を起点に解釈されます。.envのような裸のファイル名はカレントディレクトリ以下の任意の深さにマッチするので、worktreeごとにコピーされた.envも同じルールで塞がれます。.worktreeincludeで.envを新しいworktreeにコピーしている構成でも、Claudeに読ませない運用とは両立します。コピーの書き方はworktreeincludeの解説にあります。
効いているかを確かめる
設定したら、Claudeに実際に読ませて確認します。
- 1
`.env`を読ませる
対話セッションで「
.envの中身を読んで」と頼み、拒否されることを見ます。 - 2
`cat`で読ませる
「
cat .envを実行して」と頼み、同じく拒否されることを見ます。 - 3
例外を確かめる
「
.env.exampleを読んで」と頼みます。例外の行を入れたなら読めます。 - 4
出どころを見る
/permissionsでルールの出どころを確認します。
拒否の文言はバージョンや経路で変わるため、挙動のほうで判断します。.envの置き場所が別のサブディレクトリなら、そこでも試します。
なお、CLAUDE.mdへの「.envは読まない」という一文は、Claudeの振る舞いを寄せる補助にはなりますが、権限は変えません。権限を強制するのはモデルではなくClaude Codeで、CLAUDE.mdの指示は何を試みるかを形づくるだけです。強制したいものはdenyに書きます。
まとめ
Read(.env)とRead(.env.*)の2行を土台に、.env.exampleを読ませたいときだけRead(!.env.example)を後ろに足します。ただしgrep -rやスクリプト経由は対象外です。値そのものが漏れると困る秘密なら、denyだけで済ませずサンドボックスのdenyReadまで入れるかを先に決めます。.claudeignoreには頼れません。