「まず動く」を重視して高速にアプリを組み上げていく“バイブコーディング”は、現代的な開発スタイルとして人気を集めています。
特にSupabaseやFirebaseなどのBaaSを使えば、わずか数行のコードで認証やDB操作も完了し、素早いプロトタイピングやUI改善にとても便利です。
ですがその反面、「フロントエンドにAPIキーやシークレット情報を直接書いてしまう」という危険なミスも起こりやすく、思わぬハッキング被害や高額請求につながるリスクも潜んでいます。
本記事では、実際に起きた事例やデモを通じて、「なぜフロントに秘密鍵を書いてはいけないのか?」を分かりやすく解説します。
また、後半ではバイブコーディングとセキュリティを両立させるための具体的な対策や設計上のポイントも紹介します。
Webアプリ開発者なら一度は通るべきセキュリティのバイブル。
攻撃の原理と対策を体系的に学び、安全なコードを書く土台を固めましょう。
※リンクはアフィリエイトを含みます。書籍購入の参考になれば幸いです。
- 静電容量無接点方式ならではのスコスコとした打鍵感!
- REALFORCE初のワイヤレス対応!有線接続も可能!
- HHKBと違って、日本語配列に癖がなく誰でも使いやすい!
- サムホイールが搭載、横スクロールがかなり楽に!
- 静音性能も高く、静かで快適!
- スクロールは、高速モードとラチェットモードを使い分け可能!
バイブコーディングとは?効率化と引き換えに見落としがちなリスク
最近よく耳にする「バイブコーディング」。
ざっくり言えば、「とにかく動かす」を優先して、AIや便利ツールに支えてもらいながらテンポよく開発していくスタイルのことです。
たとえば、Supabase や Firebase を使えば、ログイン・DB接続・表示までがフロントエンドから一気通貫で書けて、すぐにブラウザで結果が見える。
この気持ちよさを知ると、設計やセキュリティを深く考える前に「とりあえず動いたから、これでOK!」と突っ走ってしまう人も多いはずです。
でも、そのままのノリでデプロイしてしまうと、思わぬ落とし穴が待っていることもあります。
実際、APIキーなどの大事な情報をフロントから丸見えのまま公開してしまっていた…なんて事例もあとを絶ちません。
「とりあえず動いた」ままデプロイして、やばい情報を世界に公開してる人、思ったより多いです。
その書き方、実はアウトかも?
AIにコードを手伝ってもらうのが当たり前になった今、書いてる本人が危険に気づけないケースも増えています。
動くし、エラーも出ないし、AIが出してきたコードだし。。。となれば、なおさら疑わないですよね。
たとえば、APIキーやトークンをそのままフロントエンドに書いてしまったり、バックエンドを作らずに全部フロントだけで処理させてしまったり。
その場では動いても、実は外部から簡単に悪用できる状態になっていることがあります。
こうした「知らずにやってしまうこと」の積み重ねが、情報漏洩や高額請求といった深刻なトラブルにつながることもあるのです。
フロントエンドにAPIキーを書くと何が起こるか?
バイブコーディングでは、ログイン処理や外部APIへの接続まで、すべてフロントエンドだけで完結するコードが生成されることがよくあります。
このときAIが“当たり前のように”提案してくるのが、APIキーや認証トークンなどのシークレット情報をフロント側に持たせる構成です。
一見すると正しく動いているし、見た目にも違和感はありません。
でもこの構成のままデプロイしてしまうと、本来サーバー側で安全に扱うべきシークレットが、ユーザーのブラウザから見える状態で公開されてしまいます。
シークレットがフロントにあると、何が起きるか?
- ブラウザからアクセスすれば簡単に抜き取れる
→ 画面に表示されていなくても、通信内容やコードを見ればキーは取得できます。 - そのキーで外部サービスを“他人”が操作できる
→ DBアクセス・AI API・メール送信など、本来制限されている操作がフルオープンに。 - 従量課金APIだと、即・高額請求
→ ChatGPTや外部メール配信など、不正利用で一晩に数万〜数十万円の請求も。 - シークレットの漏洩=データ漏洩に直結する場合も
→ たとえば Supabase のように、キー1つでDB全体が読めてしまう設定になっているケースもあります。
「フロントにシークレットを置く」=「自宅の鍵を玄関ドアに貼り付けて出かける」ようなものです。
実際に起きた被害事例|APIキー流出が招いたトラブル
「APIキーがフロントにあるだけで、そんなにヤバいの?」と思うかもしれません。
しかし、実際にバイブコーディングから生まれたプロジェクトで、APIキーが原因で深刻な被害が発生したケースは複数あります。
Lovable.dev から発生したリアルな被害
AIでの“バイブコーディング”プラットフォームとして注目されている Lovable.dev。
ノーコードに近い手軽さでアプリを構築できる一方で、セキュリティ設計が置き去りになるケースが相次いでいます。
Lovable.dev を使って公開された一部のアプリでは、認証キーやメールアドレスといった機密情報がそのまま公開状態になっていた事例が複数報告されています。
実際、OpenAI API や Google Maps API などのシークレットキーがフロントエンドに埋め込まれていたことで、不正利用される被害も確認されています。
中には、たった1日で数万円分のリクエストが発生したという深刻なケースも報告されているようなので、特に有料 API を使うプロジェクトでは即座に財務インパクトが出る可能性があります。
vibe-coded サイトの半数で API キーが露出
別の調査では、2,000件以上の vibe-coded サイトをスキャンした結果、約49.5% でAPIキーやJWT、Google APIキーなどのシークレットがフロントから丸見えの状態だったことが明らかになっています。
これは、コードを「動かすこと」だけを優先し、セキュリティの基本設計が欠けているケースが非常に多いことを示しています。

簡単なデモ|あなたのAPIキーは本当に見えていないか?
ここでは、APIキーの扱い方について、よくある実装パターンを3つ紹介します。(誤った例を2つと、正しい例を1つ)
一見「これで動けば大丈夫」と思える書き方でも、実際には重大なセキュリティリスクにつながる場合があります。
- 誤った例①:フロントにAPIキーを直書き
よくあるダメな例。コードを見れば誰でもキーが分かってしまいます。 - 誤った例②:.env にAPIキーを定義
たまに「.envで定義したら問題ない」と言う方もいるが、誤った例①と変わらない。 - 正しい例:APIを経由する
サーバー側で.env
を読み込み、API経由でフロントと通信する形にすることで、キーがユーザーに公開されることはありません。
誤った例①:フロントにAPIキーを直書き
まず一番やってはいけないのが、コード内にキーを直書きする方法です。
// app/page.tsx
"use client";
import { useState } from "react";
const openaiApiKey = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // ← ⚠️ 危険!フロントに直書き!
export default function Page() {
const [input, setInput] = useState("");
const [response, setResponse] = useState("");
const handleSubmit = async () => {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${openaiApiKey}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: input }],
}),
});
const data = await res.json();
const text = data.choices?.[0]?.message?.content ?? "No response.";
setResponse(text);
};
return (
<main className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold">💬 ChatBot Demo (危険な実装例)</h1>
<input
type="text"
placeholder="メッセージを入力..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="w-full p-2 border border-gray-300 rounded"
/>
<button
onClick={handleSubmit}
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
送信
</button>
{response && (
<div className="border p-3 rounded bg-gray-100 whitespace-pre-wrap">
<strong>AIの返答:</strong> {response}
</div>
)}
</main>
);
}
フロントに直書きした場合、ビルド後の JavaScript ファイルにそのままキーが含まれます。
そのため、アプリを公開すると誰でもブラウザから確認できる状態になってしまいます。
Chrome DevTools の Sources タブを開くと、配布されているバンドルファイルをそのまま閲覧できます。
そこで sk-
のような文字列を検索すれば、直書きされた API キーがすぐに見つかってしまいます。

誤った例②:.env にAPIキーを定義
.env に書けば安心だという人も一定数いますが、フロントエンドのコードからそのまま参照してしまうと、結局はビルドされた JavaScript に埋め込まれて公開されてしまいます。
例えば、下記のようなコードを作った場合:
.env
# .env
NEXT_PUBLIC_OPENAI_KEY=sk-yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
page.tsx
// app/page.tsx
"use client";
import { useState } from "react";
const openaiApiKey = process.env.NEXT_PUBLIC_OPENAI_KEY; // ← ⚠️ 危険!.envでもこれではNG
export default function Page() {
const [input, setInput] = useState("");
const [response, setResponse] = useState("");
const handleSubmit = async () => {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${openaiApiKey}`,
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: input }],
}),
});
const data = await res.json();
const text = data.choices?.[0]?.message?.content ?? "No response.";
setResponse(text);
};
return (
<main className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold">💬 ChatBot Demo (危険な実装例)</h1>
<input
type="text"
placeholder="メッセージを入力..."
value={input}
onChange={(e) => setInput(e.target.value)}
className="w-full p-2 border border-gray-300 rounded"
/>
<button
onClick={handleSubmit}
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
送信
</button>
{response && (
<div className="border p-3 rounded bg-gray-100 whitespace-pre-wrap">
<strong>AIの返答:</strong> {response}
</div>
)}
</main>
);
}
process.env.NEXT_PUBLIC_OPENAI_KEY
を参照すると、その値はビルド時に文字列として展開されます。
つまり、.env
に書いたつもりでも、実際にはフロントの JavaScript ファイルに含まれてしまい、ユーザーが自由に見られる状態です。
ブラウザの Sources タブで配布されたバンドルを開くと、sk-
で始まるキーがそのまま埋め込まれているのを確認できます。.env
を使っていたとしてもフロントエンドから参照している限り、結果的には 直書きと同じ危険性 が残ってしまいます。

さらに API を呼び出す際には、リクエストの Authorization ヘッダーにキーがそのまま付与されます。
そのためブラウザの Network タブを開けばすぐに確認・コピーできてしまい、利用者全員にキーを公開しているのと変わりません

正しい例:APIを経由する
.env
に定義したキーはサーバー側だけで読み込み、フロントエンドからは 直接参照しない ようにします。
Next.js の API Routes や Route Handlers を経由すれば、クライアントから送られるのは「ユーザーの入力」だけで、OpenAI APIキーは一切公開されません。(Vercel などにデプロイしても同じ構成で安全に動作します)
例えば、下記のようなコードを作った場合:
.env
OPENAI_API_KEY=sk-zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
route.ts
// app/api/ai/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const { input } = await req.json();
const apiKey = process.env.OPENAI_API_KEY; // サーバー側だけなので、公開されない
if (!apiKey) {
return NextResponse.json(
{ error: "Server misconfig: OPENAI_API_KEY missing" },
{ status: 500 }
);
}
const r = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [{ role: "user", content: input }],
}),
});
const data = await r.json();
return NextResponse.json(data, { status: r.status });
} catch (e) {
console.error(e);
return NextResponse.json({ error: "Bad request" }, { status: 400 });
}
}
page.tsx
// app/page.tsx
"use client";
import { useState } from "react";
export default function Page() {
const [input, setInput] = useState("");
const [response, setResponse] = useState("");
const handleSubmit = async () => {
const res = await fetch("/api/ai", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ input }),
});
const data = await res.json();
const text = data.choices?.[0]?.message?.content ?? "No response.";
setResponse(text);
};
return (
<main className="max-w-xl mx-auto p-4 space-y-4">
<h1 className="text-2xl font-bold">💬 ChatBot Demo(安全なAPI経由)</h1>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
className="w-full p-2 border border-gray-300 rounded"
/>
<button
onClick={handleSubmit}
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
>
送信
</button>
{response && (
<div className="border p-3 rounded bg-gray-100 whitespace-pre-wrap">
<strong>AIの返答:</strong> {response}
</div>
)}
</main>
);
}
もちろんフロントには APIキーは存在しないため、Sources タブを見ても見つかりません。

また、Network タブにも Authorization ヘッダーは含まれず、サーバー側が代理で OpenAI にリクエストしているので利用者に漏洩することもありません。

対策:サーバーから代理リクエストしよう
ここまでの例からもわかるように、フロントエンドでAPIキーを扱うのは極めて危険です。
APIキーは 必ずサーバー側でのみ読み込むようにしましょう。
フロントエンドからはユーザーの入力だけをサーバーに送信し、サーバーが代わりに OpenAI API にリクエストする形にすれば、APIキーが外部に漏れることはありません。これが最もシンプルかつ安全な運用方法です。
- ❌ 直書き → ソースコードや通信内容から誰でもキーをコピー可能
- ❌ .env → ビルド後に埋め込まれ、結局誰でも見える
- ✅ サーバー経由の代理リクエスト → キーは一切公開されない
たとえ一瞬でも外部に漏れてしまえば、不正利用によって数十万〜数百万円の請求が発生することも現実に起こり得ます。
他にも、たとえば .env
ファイルを GitHub にうっかり公開してしまったり、フロントエンドのコードにコメントアウトで APIキーを残していたりなど、思わぬ形で漏洩してしまうリスクはたくさんあります。
気づかないうちに外部に公開されてしまうこともあるため、APIキーなどのシークレットの扱いには常に注意が必要です。
この方法を取り入れてセキュリティ対応をきちんと行った記事を私自身でも公開しています。
詳しいコードやデプロイ例を見たい方は、ぜひこちらを参考にしてみてください。

バイブスでコーディングしてるなら、これは最低限読め!
ここまで読んで「やば、セキュリティ意識してなかったかも」と思った人。安心してください、今からでも間に合います。
Webアプリを開発するなら、どんな立場でも一度は読んでおくべきバイブルがあります。
それは、『体系的に学ぶ 安全なWebアプリケーションの作り方 第2版』(著:徳丸浩)です。
セキュリティの本質である「なぜそれが危険なのか」「どう防ぐべきか」を、脆弱性の原理から実例・対策まで徹底的に解説してくれます。
攻撃の流れや防御のポイントが具体的に学べる、まさに“体系的”な一冊。
手を動かしての実践まではしなくても、最低限の知識をインプットしておくだけで大きな事故は防げます。
数千円で数十万〜数百万円レベルのリスクを防げるなら、読まない理由はないはずです。
Webアプリ開発者なら一度は通るべきセキュリティのバイブル。
攻撃の原理と対策を体系的に学び、安全なコードを書く土台を固めましょう。
※リンクはアフィリエイトを含みます。書籍購入の参考になれば幸いです。
もし内容が難しく感じたら、経験のあるエンジニアにチェックをお願いするのも立派な対策です。
油断せず、基本を押さえておきましょう。
最後に|いますぐ確認しておこう
本記事でも解説してきたように、APIキーをフロントエンドで扱うのは非常に危険です。
見た目上は正しく動いていたとしても、実は「全世界にAPIキーを公開していた」というケースは決して珍しくありません。
たとえば、あなたが“バイブスでコーディング”したアプリ、その仕組みを本当に理解していますか?
もし理解が曖昧なままなら、今すぐに設計を見直し、安全な構成になっているかどうかを確認すべきです。
AIの力を借りてコードが書けるようになり、「これなら自分にもできる」と感じている人も多いはず。
たしかに見た目はうまく動くかもしれません。
でも、セキュリティを軽視してAPIキーが漏洩すれば、高額請求が発生するリスクは現実にあります。
そのとき責任を負うのは、他でもないあなた自身です。
- 静電容量無接点方式ならではのスコスコとした打鍵感!
- REALFORCE初のワイヤレス対応!有線接続も可能!
- HHKBと違って、日本語配列に癖がなく誰でも使いやすい!
- サムホイールが搭載、横スクロールがかなり楽に!
- 静音性能も高く、静かで快適!
- スクロールは、高速モードとラチェットモードを使い分け可能!