「APIキーは環境変数に入れたから安全」——この理解が、AI で作ったアプリで最も裏切られる場所です。
環境変数に入れること自体は正しい。ただ、環境変数にはブラウザに出るものと出ないものがあり、その境目が名前の付け方だけで決まる。そしてAIは、動かすためにその境目を越えてしまうことがあります。
この記事は、その仕組みを非エンジニア向けに説明します。
1. NEXT_PUBLIC_ が何をしているのか
Next.js には、はっきりしたルールが1つあります。
環境変数の名前が
NEXT_PUBLIC_で始まると、その値はビルド時にブラウザ向けのファイルへ埋め込まれる
これは公式ドキュメントに書かれた仕様で、バグではありません。ブラウザで動かす必要がある値——たとえば公開APIのURLや、アクセス解析の測定ID——のための正規の仕組みです。
重要なのは「埋め込まれる」という部分です。実行時にどこかから安全に読み込んでいるのではなく、ファイルの中に文字列としてそのまま書き込まれます。
【NEXT_PUBLIC_ が付いていない】
サーバーの中だけで読まれる → ブラウザには一切届かない
【NEXT_PUBLIC_ が付いている】
ビルド時にJavaScriptファイルへ文字列として埋め込まれる
→ ページを開いた人なら誰でも読める
実際に見る方法
自分のアプリで確認できます。
- アプリをブラウザで開く
- F12(Mac は ⌘+Option+I)で開発者ツールを開く
- 「Sources」タブでJavaScriptファイルを開き、鍵の一部を検索する
あるいはもっと簡単に、**ページのHTMLを右クリック→「ページのソースを表示」**して検索してもいい。埋め込まれていれば、そのまま文字列で出てきます。
2. なぜAIが境界を越えるのか
AI が悪意を持っているわけではありません。仕組みの問題です。
アプリを作っていて「データが取れません」というエラーが出たとします。あなたはAIに「エラーが出た、直して」と頼みます。AIは動く状態にするための最短経路を探します。
そのとき、ブラウザ側のコードから鍵を使いたい場面で、NEXT_PUBLIC_ を付ければ確実に動く。付けなければ undefined(値が無い)になって動かない。
AIは「動作すること」を目標にしているので、動く方を選びます。 そして動きます。エラーは消えます。あなたは直ったと思う。
これが、動くことと安全であることが正反対を向いている典型例です。
GitGuardian の2026年の調査では、AIの支援を受けて書かれたコミットは、人間だけのコミットに比べて約2倍の頻度(3.2% 対 1.5%)で秘密情報を漏らしていました。個々のAIが不注意なのではなく、「速く動かす」という目標設定の当然の帰結です。
3. 2種類の鍵 — anon と service_role
Supabase を使っている場合、鍵は2種類あります。この違いが分かれば、判断の8割は済みます。
anon キー | service_role キー | |
|---|---|---|
| 想定 | 公開される前提 | 絶対に秘密 |
| RLS | 制限を受ける | すべて無視する |
| ブラウザに置く | ○ 正常 | ✕ 致命的 |
| 用途 | 通常のアプリからの利用 | サーバー側の管理処理 |
anon キーが見えても問題ない理由
anon キーは「データベースに話しかける権利」でしかありません。何を見られるかは RLS が決めます。RLS が正しく設定されていれば、このキーを持っていても他人のデータは1行も取れません。
だから、ブラウザで anon キーが読めること自体は正常です。心配する必要はありません。
service_role キーが致命的な理由
service_role キーは、RLSを含むすべての制限を無視できる管理者用の鍵です。
これが漏れると、RLS を完璧に設定していても意味がありません。全部素通りされます。全テーブルの読み取り・書き換え・削除が、誰にでもできるようになります。
なぜAIは service_role を使いたがるのか
理由は明快です。エラーが出ないからです。
anonキーを使う → RLSポリシーを正しく書くまでデータが取れず、エラーになるservice_roleキーを使う → ポリシーを一切書かなくても、最初から全部動く
RLSを設定していない状態でAIに「データを取得する処理を書いて」と頼むと、anon では動かないので、AIは動く方を選ぶことがあります。
そして、その選択は画面上は完全に正常に見えます。
4. GitHub に上げた鍵 — 消しても消えない
もう1つの主要な漏洩経路です。
.env というファイルに鍵を書き、それを GitHub にアップロードしてしまう。よくあります。
問題は、GitHubは公開・非公開にかかわらずコミット履歴を保持することです。あとからファイルを削除しても、過去のコミットを開けば鍵はそのまま読めます。
コミット1: .env を追加(鍵が入っている) ← ここに永久に残る
コミット2: .env を削除 ← 現在の状態からは消える
「消したから大丈夫」は成立しません。
対策は1つだけ
鍵そのものを作り直すことです。各サービスの管理画面で古い鍵を無効化し、新しい鍵を発行して、環境変数を更新します。
無効化まで済ませて初めて「対応済み」です。新しい鍵を作っただけで古い鍵を無効化していなければ、古い鍵はまだ生きています。
あわせて .gitignore を確認
.gitignore は「このファイルはGitHubに上げない」というリストです。ここに .env* が入っているか確認してください。入っていなければ追加します。
5. 見落としやすい漏洩経路
NEXT_PUBLIC_ と GitHub 以外にも、実際によくある経路があります。
AIとのチャットに貼ったコード。 「このエラーを直して」と、鍵が入ったままのファイルを貼り付けたことはありませんか。多くのサービスでは学習に使われない設定がありますが、貼った時点で「自分の管理外に出た」ことは事実です。
質問用のスクリーンショット。 ターミナルや管理画面のスクリーンショットに、鍵の一部が写り込むことがあります。SNSや技術系のQ&Aサイトに投稿していれば、それは公開です。
エラーメッセージのログ。 デバッグのために「リクエストの中身を全部出力する」コードをAIが書くことがあります。そのログに認証トークンが含まれていると、ログの保管場所ごと漏洩の対象になります。
フロントエンドのソースマップ。 本番ビルドでソースマップを公開していると、圧縮されたコードが元の形に戻せます。埋め込まれた値は当然読めます。
原則はシンプルです。鍵は「見られた可能性がある」時点で無効。迷ったら作り直してください。作り直すコストは数分、漏れたときのコストは計り知れません。
6. 自分で確認する手順
手順1: NEXT_PUBLIC_ の棚卸し
プロジェクト内を NEXT_PUBLIC_ で検索し、出てきた値を1つずつ見ます。判断基準はこれだけです。
この値が、見ず知らずの他人の手元にあっても平気か?
- 公開APIのURL → 平気
- アクセス解析の測定ID → 平気
- Supabase の
anonキー → 平気(RLSが前提) - Supabase の
service_roleキー → アウト - データベースのパスワード → アウト
- OpenAI / Anthropic などのAPIキー → アウト
- Stripe の秘密鍵(
sk_で始まるもの)→ アウト
Stripe の場合、pk_ で始まる公開可能キーはブラウザに置いてよく、sk_ で始まる秘密鍵は絶対に置いてはいけません。名前で見分けられます。
手順2: ブラウザで実際に探す
本番のアプリを開いて、ページのソースを表示し、鍵の先頭数文字で検索します。出てきたら、その鍵は世界中から読める状態です。
手順3: GitHub を確認
リポジトリのファイル一覧に .env が無いか見ます。あれば、そのファイルの「History(履歴)」も確認してください。
7. AIに貼る修正指示
このプロジェクト全体を調べて、次の3つを確認してください。
(1) NEXT_PUBLIC_ が付いた環境変数に秘密情報が入っていないか
(Supabase の service_role キー、データベースのパスワード、
OpenAI/Anthropic などのAPIキー、Stripe の sk_ で始まる秘密鍵)
(2) コードの中に鍵やパスワードが直接書かれていないか
(3) .gitignore に .env* が含まれているか。含まれていなければ追加する
見つかった場合は、その値をサーバー専用の環境変数(NEXT_PUBLIC_ を
付けない)に移し、参照しているコードをサーバー側(Route Handler /
Server Action)へ移動してください。クライアントコンポーネントから
秘密情報を参照している箇所は、すべて一覧にしてください。
重要: 値そのものは絶対に出力しないでください。「どのファイルの何行目に
どの種類の鍵があるか」だけを報告してください。
最後に、見つかった鍵それぞれについて「どのサービスの管理画面で作り直せ
ばよいか」と「作り直したあとに更新が必要な環境変数」を教えてください。
最後の一文が重要です。移動しただけでは対策になりません。一度でも公開側に出た鍵は、作り直すまで有効なままです。
8. 正しい配置のかたち
整理すると、こうなります。
【ブラウザに出てよい】NEXT_PUBLIC_ を付ける
- 公開APIのURL
- Supabase の URL と anon キー(※RLSが正しく設定されている前提)
- アクセス解析の測定ID
- Stripe の pk_ で始まる公開可能キー
【サーバーの中だけ】NEXT_PUBLIC_ を付けない
- Supabase の service_role キー
- データベースの接続文字列・パスワード
- OpenAI / Anthropic などのAPIキー
- Stripe の sk_ で始まる秘密鍵、Webhook の署名シークレット
- メール送信サービスのAPIキー
そして本番の値は、Vercel などホスティング側の環境変数画面にだけ入れます。.env ファイルは手元の開発用で、GitHubには上げません。
まとめ
NEXT_PUBLIC_が付いた値は、ビルド時にブラウザ向けファイルへ埋め込まれる。仕様であってバグではない- Supabase の
anonキーは公開前提で安全。service_roleキーは全制限を無視するので絶対にブラウザに置けない - AI が
service_roleを使いがちなのは「RLSを書かなくてもエラーが出ない」から。楽な道が最も危険 - GitHubのコミット履歴は消えない。ファイルを消すだけでは対策にならず、鍵の作り直しが必須
- AIチャットへの貼り付け、スクリーンショット、ログも漏洩経路。「見られたかも」の時点で作り直す
鍵の配置は、仕組みさえ分かれば判断できます。基準は1つ、「他人の手元にあっても平気か」。迷ったらサーバー側に置いてください。
RLS とあわせて確認したい方は Supabaseの「RLS」って何?、全体像は AIで作ったアプリを公開する前に をどうぞ。