環境変数と.envとは AIに鍵を預ける前に知っておくこと

昨日書いた広告運用の記事で、鍵の扱いについて一文だけ触れました。認証情報は .env に置いて .gitignore で除外している、ただしこれは誤ってコミットするのを防ぐだけの措置で、これで鍵が安全になるわけではない、と。
その先を書かないまま終わってしまったので、今日はそこを書きます。
AIに仕事を任せると、外部のサービスにつなぐ場面が出てきます。そこで認証が必要になり、たいていはAPIキーやトークンを用意することになる。秘密の値そのものをAIに見せずに済ませる作り方もありますが、手元でAIにツールを動かしてもらう使い方だと、鍵の書かれたファイルが同じフォルダにある状態から始まるのが普通です。この記事はその前提で書きます。
これまで鍵の事故といえば、人がうっかり公開してしまうものが中心でした。いまはそこに、AIが読んで別のどこかに書き出す、という経路が加わっています。
環境変数は、変わる値だけをプログラムの外に出す仕組み
先に環境変数から説明します。ここが曖昧なままだと、この先の話が全部ぼやけます。
プログラムには、外とやりとりするための設定値がたくさん入っています。どこに接続するか、どのアカウントを使うか、そしてAPIキー。これをプログラム本体に直接書き込むと、困ることが起きます。
わかりやすいのは、テスト用と本番用の使い分けです。テスト中に本番のデータへ書き込んだら事故ですよね。だから接続先を分けたい。でもプログラム本体に本番の宛先が書いてあると、テストのたびに書き換えることになります。そして、書き換え忘れた日に事故が起きる。
そこで、変わる部分だけをプログラムの外に出すという考え方が生まれました。これが環境変数です。
やっていることは、名前と値の組を用意しておくことだけです。同じ名前に、動かす場所ごとに違う値を入れておきます。
手元のパソコンで動かすとき。
SEND_TO=テスト用の宛先
API_KEY=テスト用のキー
本番のサーバーで動かすとき。
SEND_TO=本当の宛先
API_KEY=本物のキー
プログラム本体には「SEND_TO の値を使う」とだけ書いておきます。実際の値は動かすときに外から差し込む。手元なら上の組が、本番なら下の組が入る。プログラム本体は1文字も変わりません。
ここで正確に書いておきたいことがあります。値を「プログラムが知らない」わけではありません。 動いている最中のプログラムは、渡された値を読めます。読めなければ使えませんから、当然です。プログラム本体に値が書かれていない、というだけの話です。細かいようですが、これは後半で必要になる区別です。
.env は金庫ではなく、鍵をまとめて置く引き出し
環境変数を渡す方法はいくつかありますが、開発中によく使われるのが .env という名前のファイルです。中身はこういう、ただのテキストです。
SEND_TO=test@example.com
API_KEY=abcd1234efgh5678
1行に1組。イコールの左が名前、右が値です。
ひとつ補足を。.env は置いておくだけでは何も起きません。 プログラムを起動するときにこのファイルを読んで、中身を渡してやる役が別に要ります。その役は言語やフレームワークによって違いますし、.env の書き方に統一された決まりがあるわけでもありません。ファイルはあくまで値の書き置きです。
そして肝心なところ。.env は暗号化されていません。 パスワードもかかっていません。そのファイルを読める権限を持つ人やプログラムなら、開いた瞬間に平文で読めます。
つまり、APIキーを .env に入れる作業は、鍵を金庫にしまう作業ではありません。あちこちに散らばっていた鍵を、1つの引き出しに集める作業です。
集めること自体には価値があります。どこに何があるか分かるようになるし、気にすべき場所が1つに絞られる。でも、集めたことと守ったことは違います。
環境変数は「値を外から渡す仕組み」で、.env は「その値を書いておくファイルのよくある名前」です。そして守るべき秘密には、ファイルに書いてある状態と、動いているプログラムに渡された状態の2つがあります。この記事の後半は、ほとんどこの2つの区別の話になります。
.gitignore が守るのは「これから入れない」だけ
守る側へ移ります。.env とよく一緒に出てくるのが .gitignore です。
この先はGitの用語が出てくるので、先に簡単に説明しておきます。Gitはファイルの変更履歴を保存する仕組み、コミットはその履歴に保存地点を作る操作、プッシュはその履歴をGitHubのようなサーバーへ送る操作です。この3つは別物で、あとの説明では区別が必要になります。
.gitignore は「このファイルは履歴に入れない」と書いておくメモです。ここに .env と書いておけば、コードと一緒に鍵まで送ってしまう事故を防げます。まずやるべきことではあります。
ただ、守備範囲は名前のとおりです。これから入れないようにするだけで、すでに入ってしまったものには効きません。 一度コミットした値は履歴に残り続けるので、後から .gitignore に書き足しても消えません。
ここで順番を間違えないでほしいのですが、すでに外へ送ってしまった鍵に対して、最初にやるべきことは履歴を消すことではありません。 その鍵を無効にして、新しいものに差し替えることです。GitHubの手順書も、履歴の書き換えは影響が大きいと断ったうえで、公開されてしまった認証情報は無効になっている前提で案内しています(GitHubの公式手順)。履歴から消しても、すでに誰かの手元へ複製されている可能性のほうは消えません。
やることは3つに分かれます。今後コミットしないようにする。過去の履歴から消す。すでに出てしまった鍵を無効にする。急ぐのは3つめです。
GitHub側にも網はあります。プッシュ保護という仕組みで、鍵らしき文字列を含むプッシュをサーバーに届く前に止めてくれます。ただし、止まるのは対応済みのパターンとして検出できたものだけです。独自の形式で発行された鍵や、検出漏れはあります。
そのうえで、この保護は2種類あります。個人アカウント向けのものは既定で有効ですが、守るのは公開リポジトリへのプッシュだけです。リポジトリ単位のものは既定で無効で、有効にするには別途GitHubのシークレット保護機能が要ります。そしてそちらは、書き込み権限のある人が理由を付けて通すこともできます。非公開だから守られている、ではありません。
ここまでは「Gitに混ざる」経路の話でした。AIが読む経路は、この層とは別のところにあります。もっとも、AIが鍵を読んでファイルに書き、それをコミットしてプッシュすれば最後にこの網へたどり着くので、事故の道筋としてはつながっています。
「読むな」と書いても、それはお願いにすぎない
昨日の記事で、AIに渡す指示を設定ファイルに書いている、という話をしました。Claude Codeなら CLAUDE.md、Codexなら AGENTS.md。役割や禁止事項をそこに置いておく運用です。
では、そこに「.env は読まないこと」と書けば、AIは読まなくなるのでしょうか。
なりません。というより、それは守られるかもしれないし、守られないかもしれない、という種類のものです。Claude Codeの公式ドキュメントは、そこをはっきり書いています。
Permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries to do, but they don't change what Claude Code allows.
(権限ルールを適用するのは Claude Code であって、モデルではない。プロンプトや CLAUDE.md の指示は Claude が何をしようとするかを形づくるが、Claude Code が何を許すかは変えない)
指示ファイルに書くのは、あくまでお願いです。方針を伝える役には立ちますが、境界線ではありません。同じページの別の箇所でも、指示ファイルによる誘導は試みることを形づくるだけで境界を強制しないので、他の手段と組み合わせるように、と念を押しています。
境界線のほうは別の仕組みです。Claude Codeの場合、設定ファイルにこう書きます。置き場所によって効く範囲が変わり、~/.claude/settings.json に書けば自分のすべてのプロジェクトに、プロジェクトの中の .claude/settings.json に書けばそのプロジェクトに効きます。後者をチーム全員に届けるには、そのファイルをリポジトリに入れて共有する必要があります。
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)"
]
}
}
これは私たちが考えた例ではなく、公式の設定ドキュメントが最初に挙げている例そのものです。秘密の入ったファイルを読ませない設定を、代表例として置いている。
ただしこの2行が指しているのは、いま作業しているフォルダの直下にある .env です。別の場所に置いた秘密のファイルや、別の名前を付けたファイルは対象になりません。自分のところのファイル配置に合わせて足す必要があります。
拒否のルールは許可より先に評価されます。同じファイルに対して後から広い許可を足しても、拒否のほうが勝ちます。 指示ファイルへのお願いと違って、ここは揺れません。
なお、この設定ファイルはJSONという形式で、カンマひとつで壊れます。書き換えに不安があるなら、自分で触るより開発の担当者に頼むほうが早いはずです。「.env の読み取りを拒否するルールが入っているか見てほしい」と伝われば通じます。
読ませない設定にも、届かない場所がある
ここまでやれば安心、と書きたいところですが、まだ早い。この設定がどこまでを止めるのかを知らないと、守られていない場所を守られていると思い込むことになります。
公式ドキュメントにはこう書かれています。読み取りと編集の拒否ルールは、AIが持つファイル操作の機能に効きます。さらに、Claude Codeが把握しているファイル系のコマンド、たとえば cat や head のような「ファイルの中身を表示する」たぐいの命令にも効きます。ここまでは止まる。
止まらないのはここからです。PythonやNodeのスクリプトが、自分でファイルを開いて読む場合には効きません。 AIがそういうスクリプトを書いて実行すれば、拒否ルールの外側を通っていきます。そこまで塞ぐには、OSの側で止めるサンドボックスという別の仕組みが要る、と公式は案内しています(Configure permissions)。
そのサンドボックスにも、外へ出る道は残っています。サンドボックスの中で動かせなかったコマンドは、通常の許可の流れに戻ります。黙って外で実行されるわけではなく、人が確認を求められる形です。それとは別に、サンドボックスそのものが使えない環境では、警告を出したうえでサンドボックスなしで動くのが既定の挙動です。厳しく締めたいなら、そこまで含めて設定を確認する必要があります(サンドボックスの設定)。
そしてもうひとつ、これが一番見落とされやすいところです。ファイルへのアクセスを止めても、すでにプログラムへ渡された値までは止まりません。 前半で分けておいた2つが、ここで問題になります。.env を読ませない設定は、あくまでファイルとして読ませない設定です。同じ値が環境変数としてAIの実行環境に渡っていれば、そちらは別の経路として残ります。
意地悪な話に聞こえるかもしれません。でも私たちは、こう書いてあるドキュメントのほうを信用します。「これで完全に安全です」と書いてあるものより、どこまでが範囲でどこからが範囲外かを書いてあるもののほうが、はるかに使いやすい。
実務的な結論はこうです。拒否ルールは入れる。ただし、それを最後の砦だと思わない。
漏れる前提で、鍵の側を弱くしておく
ここまでは読まれないようにする話でした。読まれてしまったときの話をします。昨日「別に要ります」と書いて済ませた部分です。
その鍵にできることを減らす。 発行するときに権限を選べるなら、必要な範囲だけを選ぶ。読み取りだけで足りる用途に、書き込みも削除もできる鍵を渡さない。
用途ごとに別の鍵にする。 ここは置き場所を分けるだけでは足りません。手元用とアプリ用でファイルを分けても、中身が同じ鍵なら、どちらが漏れても被害は変わりません。鍵そのものを別に発行して、片方から他方をたどれないようにして、はじめて範囲が切れます。
どこで使っているかを書き出しておく。 これが案外できていません。漏れたと分かってから「この鍵、どこで使ってたっけ」と探し始めると、止める判断が遅れます。私たちが最低限そろえているのは、鍵の名前、発行元のサービス、使っている場所、誰が発行したか、の4つです。
この一覧が役に立つのは、実際に漏れたときです。急ぐのが鍵の無効化と差し替えであることは先に書いたとおりで、そのとき使い場所が分かっていれば、止まるサービスを見込んだうえで動けます。分かっていなければ、そこから調べ始めることになります。
さいごに いま、自分の鍵がどこにあるか
まとめると、こうなります。.env は鍵を集める引き出しであって金庫ではない。.gitignore はGitに混ざるのを防ぐだけ。指示ファイルに書いた禁止はお願いで、強制するのは権限設定。その権限設定にも届かない経路があり、ファイルを止めても、実行中に渡された値までは止まらない。
こう並べると防ぎようがないように見えますが、逆です。どこまでが守られていて、どこからが守られていないかが分かっていれば、対策は選べます。 危ないのは、.env に入れた時点で終わったと思っている状態のほうです。
今日できることを1つ挙げるなら、いま使っているAIツールの設定に、鍵のファイルを読ませない指定が入っているかを確かめてみてください。自分で設定を触るより開発の担当者に聞くほうが早ければ、そのほうがいいです。
そのうえで、手元の鍵を1本選んで、これが漏れたら誰に何ができてしまうのかを言ってみる。答えがすぐ出てこないなら、次にやるのは権限を減らすことではありません。その鍵がどこで使われているかを調べるところからです。用途を知らないまま権限を絞ると、動いているものが止まります。順番だけは間違えないでください。