APIキーが外から見えてしまう3つの経路と、出てしまったときの対処
APIキーをコードから消してコミットし直したのに、GitHubで過去のコミットをたどると、前の版にそのまま残っている。これは事故というより、Gitの仕組みどおりの動きです。
この記事では、APIキー(外部サービスを使うための合言葉のような文字列)が、隠したつもりでも外から見えてしまう代表的な経路を3つと、出てしまったときにやることを整理します。「キーは環境変数に置く」というところまでは知っている人でも、移す前の履歴や、移したあとの扱いで漏れる経路が中心です。
例に出すキーはすべて架空の値です。Gitの動きは Git 2.54 で実際に試して確認しました。
経路1:コードに直書きして push した(あとで消しても履歴に残る)
まず押さえたいのがこれです。動作確認のつもりで、キーをそのままコードに書いてしまうパターンです。
// api.js(1回目のコミット)
const apiKey = "dummy-aaaa-1111";
あとで気づいて環境変数に移し、コミットし直したとします。
// api.js(2回目のコミット)
const apiKey = process.env.MY_API_KEY;
今のファイルからはキーが消えました。でも Git は「これまでの全部の版」を保存する仕組みなので、1回目のコミットには前の中身が残っています。キーの文字列で履歴を検索すると、ちゃんと見つかります。
git log -S "dummy-aaaa" --oneline
# 21aa28f 環境変数に移動 ← キーが消えたコミット
# be53a21 API呼び出しを追加 ← キーが入ったコミット
GitHubに push していれば、公開リポジトリなら誰でもこの過去の版を開けます。「キーを消したコミットを足す」だけでは、キーは隠れません。
経路2:.env.local をコミットしてから .gitignore に書いた
キーを .env.local に移すのは正しい対処です。ただし順番を間違えると、Gitへの登録は止まりません。
.gitignore は「まだGitに入っていないファイルを、これから無視する」ための設定です。すでに一度コミットしたファイルは、あとから .gitignore に書いても管理対象のまま残ります。実際に試すと、コミット済みのファイルは .gitignore に書いたあとも、書き換えるたびに「変更あり」と表示され続けました。
git status --short
# M .env.local ← 無視されず、変更として出てくる
管理対象から外すには、手元のファイルは残したまま、Gitの記録からだけ外すコマンドを使います。
git rm --cached .env.local
git commit -m ".env.local を管理対象から外す"
これで次からは無視されます。ただし経路1と同じで、過去のコミットに入った中身は残ったままです(push 済みなら、外からも見えます)。
.gitignore に書いたのに効いているか不安なときは、git check-ignore -v .env.local を実行してみてください。何も表示されない場合は、まだ管理対象に残っているか、.gitignore の書き方が合っていないサインです。
経路3:NEXTPUBLIC を付けて、ブラウザに配ってしまった
経路1・2のようにキーを環境変数へ移すと、画面側のコードでは値が undefined になることがあります。そこで「名前に NEXT_PUBLIC_ を付けたら動いた」で終わらせると、3つ目の経路が開きます。
Next.js では、名前に NEXT_PUBLIC_ を付けた環境変数を画面側のコードで使うと、その値がビルド(公開用にコードをまとめる作業)のときに、画面側のJavaScriptへ埋め込まれます。つまり、サイトを開いた人なら誰でも、ブラウザの開発者ツールから読める状態になります。
これは「見られても困らない値」を画面側で使うための仕組みです。たとえば Supabase では、ブラウザに出る前提の鍵と、絶対に出してはいけない鍵が分かれています。
NEXT_PUBLIC_SUPABASE_ANON_KEY… ブラウザに出る前提の鍵。データを守るのは RLS(行ごとに「誰が読めるか」を決める設定)の役目SUPABASE_SERVICE_ROLE_KEY… RLS を素通りできる強い鍵。NEXT_PUBLIC_を付けてはいけない
「画面から読めないとエラーになるから」と、強い鍵に NEXT_PUBLIC_ を付けて解決したことにすると、玄関の鍵をドアに貼っておくのと同じ状態になります。
画面から強い鍵が必要に見えたら、まず RLS のポリシーで「ログイン中の本人なら読める」と許可できないかを見直します(読めない原因が、鍵ではなくポリシーの側にあることもあります)。それでも強い鍵が要る処理だけをサーバー側(Route Handler や Server Action といった、サーバーで動く処理の置き場所)に移し、そこでも「呼び出した人がその操作をしてよいか」を確かめます。
なお、サーバー側で読んだ強い鍵を、画面側の部品に props(部品に渡すデータ)でそのまま渡した場合も、NEXT_PUBLIC_ を付けていなくてもブラウザに届きます。鍵はサーバー側の処理の中だけで使い、画面側には結果だけを渡します。
一度でもこの状態で公開していたら、NEXT_PUBLIC_ を外して直すだけでは足りません。配られたJavaScriptに鍵が入っていた以上、次の「鍵の作り直し」が必要です。
出てしまったら:消すより、鍵を作り直す
キーが外に出たと気づいたら、最初にやるのは履歴の掃除ではなく、発行元のサービスでそのキーを無効にして、新しいキーを発行し直すことです。
- 公開リポジトリに一度でも push したキー、公開したサイトに埋め込まれたキーは、もう誰かにコピーされた前提で考える
- 新しいキーは
.env.localに入れ、本番(Vercel など)の環境変数も新しい値に更新して、再デプロイする(環境変数の変更は、次のデプロイから反映されます) - 履歴からの削除は、その後で必要に応じて行う(消しても、すでにコピーされた分は取り戻せないため)
- 作り直しの手順や、作り直したときに一緒に変わるもの(ほかの鍵やログイン状態など)はサービスごとに違うので、発行元の案内に従う
GitHub にも、公開リポジトリへの秘密情報の push を止める仕組みがあります。ただ、すべての種類のキーを見分けられるわけではないので、それだけに頼るのは危険です。
AIが書いたコードを、コミットのときに見る場所
AIに「とりあえず動くようにして」と頼むと、手早く動かすために、キーを直接コードに書いた版が出てくることがあります。AIは速く書いてくれる分、「これを公開して大丈夫か」を見る目は人の側に残ります。コミットのときは、次の2つだけ見る習慣をつけると安心です。
git status # .env.local が一覧に出ていないか
git diff --staged # これから記録する行に、キーらしい長い文字列や、NEXT_PUBLIC_ が付いた強い鍵の名前がないか
AIに頼むときも「キーは環境変数から読む形で書いて。画面側(ブラウザで動くコード)には置かず、NEXTPUBLIC も付けないで」と一言添えておくと、3つの経路のどれかに入った版が出てくるのを防ぎやすくなります。
鍵は一度外に出ると、消しても取り戻せません。出る前にコミットの入口で止める、出たら作り直す。この2つを知っておくと、AIと一緒に速く作るときの怖さはぐっと減ります。
面談なしで、今日から始められます
この講座は 未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を全24セッションで体系化した教材付きプランです。無料相談を挟まず、申し込んだその日から教材で学習を始められます。AIが学習パートナーになって何度でも質問でき、つまずいた所だけチャットで直接サポートします。
- 今日から始める(教材完全版+月5,500円・チャット質問し放題・いつでも解約OK)→ https://menta.work/plan/20251?ref=menta-knowledge
- いきなりは不安な方へ:無料の教材体験版もあります(最初の数セッション分)→ プラン詳細をご覧ください

