Chuyển đến nội dung chính
友田 陽大
Running the app you built with AI
バイブコーディング
セキュリティ
Next.js
Supabase
個人開発

NEXT_PUBLIC_ と service_role — AIが書いた「APIキー」がなぜ他人に見えるのか

AIが生成したコードでAPIキーやパスワードが他人に読める状態になる仕組みを、非エンジニア向けに解説します。NEXT_PUBLIC_ の意味、Supabaseのanonキーとservice_roleキーの決定的な違い、GitHubに一度でも上げた鍵をなぜ作り直す必要があるのか。確認手順とAIへの修正指示つき。

Published
Reading time
9 min read
Author
友田 陽大
Chia sẻ

「APIキーは環境変数に入れたから安全」——この理解が、AI で作ったアプリで最も裏切られる場所です。

環境変数に入れること自体は正しい。ただ、環境変数にはブラウザに出るものと出ないものがあり、その境目が名前の付け方だけで決まる。そしてAIは、動かすためにその境目を越えてしまうことがあります。

この記事は、その仕組みを非エンジニア向けに説明します。


1. NEXT_PUBLIC_ が何をしているのか

Next.js には、はっきりしたルールが1つあります。

環境変数の名前が NEXT_PUBLIC_ で始まると、その値はビルド時にブラウザ向けのファイルへ埋め込まれる

これは公式ドキュメントに書かれた仕様で、バグではありません。ブラウザで動かす必要がある値——たとえば公開APIのURLや、アクセス解析の測定ID——のための正規の仕組みです。

重要なのは「埋め込まれる」という部分です。実行時にどこかから安全に読み込んでいるのではなく、ファイルの中に文字列としてそのまま書き込まれます

【NEXT_PUBLIC_ が付いていない】
サーバーの中だけで読まれる → ブラウザには一切届かない

【NEXT_PUBLIC_ が付いている】
ビルド時にJavaScriptファイルへ文字列として埋め込まれる
  → ページを開いた人なら誰でも読める

実際に見る方法

自分のアプリで確認できます。

  1. アプリをブラウザで開く
  2. F12(Mac は ⌘+Option+I)で開発者ツールを開く
  3. 「Sources」タブでJavaScriptファイルを開き、鍵の一部を検索する

あるいはもっと簡単に、**ページのHTMLを右クリック→「ページのソースを表示」**して検索してもいい。埋め込まれていれば、そのまま文字列で出てきます。


2. なぜAIが境界を越えるのか

AI が悪意を持っているわけではありません。仕組みの問題です。

アプリを作っていて「データが取れません」というエラーが出たとします。あなたはAIに「エラーが出た、直して」と頼みます。AIは動く状態にするための最短経路を探します。

そのとき、ブラウザ側のコードから鍵を使いたい場面で、NEXT_PUBLIC_ を付ければ確実に動く。付けなければ undefined(値が無い)になって動かない。

AIは「動作すること」を目標にしているので、動く方を選びます。 そして動きます。エラーは消えます。あなたは直ったと思う。

これが、動くことと安全であることが正反対を向いている典型例です。

GitGuardian の2026年の調査では、AIの支援を受けて書かれたコミットは、人間だけのコミットに比べて約2倍の頻度(3.2% 対 1.5%)で秘密情報を漏らしていました。個々のAIが不注意なのではなく、「速く動かす」という目標設定の当然の帰結です。


3. 2種類の鍵 — anonservice_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で作ったアプリを公開する前に をどうぞ。

Câu hỏi thường gặp

NEXT_PUBLIC_ を付けると何が起きるのですか?
Next.js では、環境変数の名前が NEXT_PUBLIC_ で始まると、ビルド時にブラウザ向けのJavaScriptファイルへ値がそのまま埋め込まれます。実行時に読み込むのではなく、ファイルの中に文字列として書き込まれるため、ページを開いた人なら誰でも開発者ツールで読めます。これはバグではなく、ブラウザで動かす必要がある値(公開APIのURLなど)のための正規の仕組みです。
Supabaseのanonキーがブラウザから見えていますが、大丈夫ですか?
大丈夫です。anon キーは設計上「公開されるもの」で、ブラウザで読めるのは正常な状態です。このキーは RLS(行レベルセキュリティ)の制限を受けるため、RLSさえ正しく設定されていれば、他人のデータは取得できません。逆に言えば、RLSが無い状態で anon キーが公開されているのが危険な組み合わせです。
service_role キーは何が違うのですか?
service_role キーは、RLSを含むすべての制限を無視できる管理者用の鍵です。これが漏れると、RLSを完璧に設定していても意味がありません。全テーブルの読み取り・書き換え・削除がすべて可能になります。サーバー側の環境変数にのみ置き、サーバー側のコードからしか使ってはいけません。
なぜAIは service_role キーを使いたがるのですか?
エラーが出ないからです。anon キーを使うと、RLSポリシーを正しく書くまでデータが取得できずエラーになります。service_role キーを使えば、ポリシーを一切書かなくても最初から全部動きます。AI は「動作すること」を目標にしているため、最短で動く選択をしがちです。この場合、動くことと安全であることが正反対の方向を向いています。
一度GitHubに上げてしまった鍵は、ファイルを消せば大丈夫ですか?
大丈夫ではありません。GitHubは公開・非公開にかかわらずコミット履歴を保持するため、あとからファイルを削除しても過去のコミットを見れば鍵はそのまま読めます。対策は鍵そのものを各サービスの管理画面で無効化し、新しい鍵を発行することです。無効化して初めて「対応済み」になります。

Tài liệu tham khảo

友田

友田 陽大

Người phát triển sản phẩm đoạt Giải thưởng Bộ trưởng (METI). Với TypeScript + Python + AWS, tôi một mình triển khai trọn gói SaaS, chuyển đổi số công nghiệp và AI tạo sinh (RAG) sẵn sàng cho production — từ phân tích yêu cầu đến hạ tầng và vận hành.

“It works. But is it safe to launch?”

Twenty questions, no signup — a pre-launch self-check for the app you built with AI

You don't need to read any code. Answer yes / no / not sure to questions like “can only the person who logged in see their own data?” You get your risks ordered by severity, plus a fix instruction you can paste straight into Claude Code or Cursor. Nothing you type leaves your browser.

Nhận cả dự án trọn gói lẫn vai trò cố vấn kỹ thuật. Hãy bắt đầu bằng buổi tư vấn miễn phí 30 phút.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Cũng đáng đọc