# バイブコーディングのセキュリティと危険性 — AI生成コードをそのまま公開すると何が起きるか

> 昨夜AI（Claude Code / Cursor）で作ったSaaSを、今朝そのまま公開していいか。バイブコーディングで頻出する5つの危険——認可の欠落・Supabase RLSの設計ミス・注入・秘密情報のハードコード・検証ゲートの不在——を、脆弱→修正の実コードと『自分で叩いて確かめる』手順で解説。公開前セルフ診断チェックリストへ導きます。

- 公開日: 2026-07-07
- 著者: 友田 陽大
- タグ: バイブコーディング, AI駆動開発, セキュリティ, Supabase, RLS
- URL: https://tomodahinata.com/blog/vibe-coding-security-risks-self-check-guide
- カテゴリ: バイブコーディング
- 総合ガイド: https://tomodahinata.com/blog/vibe-coding-what-is-tools-risks-production-guide

## 要点

- 昨夜AIで作ったSaaSを翌朝そのまま公開する前に、認可・RLS・注入・秘密情報・検証ゲートの5点を自分の手で点検せよ。デモが動くことと本番で漏れないことは、まったく別の問題だ
- 最頻出かつ最重大は認可の欠落（IDOR）。AIは『ログイン済みか（認証）』は書くが『そのデータの持ち主か（認可）』を省く。URLのIDを他人の値に差し替えて自分で叩けば、公開前に数分で確かめられる
- Supabaseでは認可をDB側のRLSで強制する。『認証はするが行を所有者に絞らない』ポリシは頻出で、公開アプリ1,000件の一次調査でもRLSを持つアプリの約9.2%が該当した（下限値・要確認）。USING(true)はそのテーブルが公開前提のときだけ正解
- 秘密情報はハードコードしない。とりわけSupabaseのservice_roleキーはRLSを完全に無視するため、クライアントやNEXT_PUBLICに出した瞬間、全データが露出する。守りの境界はキーの置き場所に移る
- 一度直しても、次にAIが生成すれば同じ穴が再発する。水平統制の自動化は無料OSSの npx @aegiskit/cli scan に任せ、認可/RLS設計という縦のリスクは自分の目とCIゲートで確かめ続ける

---

昨夜、Claude Code と数時間でSaaSを作った。認証もついている。デモは完璧に動く。——では、**今朝これを公開していいか？**

私の答えは明確です。**「5つの点検を、自分の手で通すまでは、まだダメ」。**

これは脅しではありません。私は一人 × 生成AI（Claude Code）で、経済産業大臣賞を受賞したB2B SaaS、本番二重課金0件の決済基盤、放送局向けのAIプラットフォームを作ってきました。**だからこそ言えます——AIは速い。しかし放っておくと、脆弱性も同じ速さで量産します。** そして厄介なことに、その脆弱性はデモでは一切見えません。見えるのは、公開して、誰かが「他人のIDを指す正規リクエスト」を送ってきた瞬間です。

本記事は、バイブコーディングで**特に頻出する5類型の危険**を、脆弱コードと修正コード、そして**「自分で叩いて確かめる」手順**で示します。読み終えたら、記事末尾の[公開前セルフ診断チェックリスト](/resources)で、あなたの昨夜のコードをそのまま点検できます。

---

## 1. 「デモは動く」と「本番で漏れない」は別物 — 昨夜の自分に問う一行

まず、なぜAIが危険な穴を生むのかを一行で押さえます。**AIは、プロンプトで指示された「やりたいこと」＝ハッピーパスを驚くほど速く書く。しかし本番で問われるのは、不正な入力・同時アクセス・他人のIDを指す正規リクエストにどう振る舞うか**——ここが構造的に抜けます。

これは感想ではありません。公開された事実です。Veracode の2025年の調査では、**AIが生成したコードの45%が既知のセキュリティ欠陥を含み、モデルが賢くなってもセキュリティ成績は横ばい**でした（[Veracode 2025 GenAI Code Security Report](https://www.veracode.com/resources/analyst-reports/2025-genai-code-security-report/)）。「賢いモデルを使えば安全になる」は、残念ながら成り立ちません。

なぜAIが危険を生むかの掘り下げ（訓練データの欠陥再生産・文脈の欠如・幻覚）と、本番で壊れる箇所の全体像は、それぞれ[AI生成コードの脆弱性診断](/blog/ai-generated-code-vulnerability-assessment-vibe-coding-security-guide)と[AI生成コードの本番化（ハードニング）](/blog/vibe-coding-ai-generated-code-production-hardening-guide)で詳しく書きました。ここでは重複を避け、**「昨夜作った自分が、公開ボタンを押す前に何を自分の手で確かめるか」**に絞ります。

点検すべきは、頻出する次の5類型です。

| # | 類型 | AIがやりがちなこと | 一言で言うと |
|---|---|---|---|
| ① | 認可の欠落（IDOR） | ログインは確認するが「持ち主か」を確認しない | 認証はするが、認可しない |
| ② | RLSの設計ミス | Supabaseのテーブルにポリシを張り忘れる／緩く張る | DBが最後の砦なのに、砦が開いている |
| ③ | 注入（injection） | 文字列連結・生SQL・`dangerouslySetInnerHTML` | 外部入力を「正しい前提」で扱う |
| ④ | 秘密情報のハードコード | APIキーや `service_role` をコードに直書き | 鍵をドアに貼って出かける |
| ⑤ | 検証ゲートの不在 | 一度直しても、次の生成で同じ穴が復活 | 直したことが、次の生成で無かったことになる |

以下、1つずつ「脆弱→修正→自分で確かめる」で潰していきます。

---

## 2. 類型①：認可の欠落（IDOR）— URLのIDを1つずらすだけ

**最頻出かつ最重大です。** AIは認証（ログイン済みか）は書きますが、**認可（その人がそのデータの持ち主か）を省略**します。

```ts
// ❌ AIがよく書く：ログインは確認するが「所有者か」を確認しない
export async function GET(_req: Request, { params }: { params: { id: string } }) {
  const session = await getSession();
  if (!session) return new Response("Unauthorized", { status: 401 });

  // params.id は誰のものでもチェックしていない → 他人の注文が見える
  const order = await db.order.findUnique({ where: { id: params.id } });
  return Response.json(order);
}
```

`session` の有無だけを見て、`params.id` が**その人のものか**を一切見ていません。ログイン済みの攻撃者が `/api/orders/123` を `/api/orders/124` に変えるだけで、他人のデータが返ってきます。これが **IDOR（Insecure Direct Object Reference）／ BOLA（Broken Object Level Authorization）** で、OWASP API Security Top 10 では**2019年以来ずっと第1位**の最頻出リスクです（[OWASP API1:2023 BOLA](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/)）。

修正は、**所有権を必ずクエリに含める**ことです。

```ts
// ✅ 「持ち主か」をクエリで強制する
const order = await db.order.findFirst({
  where: { id: params.id, userId: session.userId }, // 所有者スコープを付与
});
if (!order) return new Response("Not Found", { status: 404 });
return Response.json(order);
```

**自分で確かめる手順（数分で終わる）:**

1. 2つのアカウントA・Bでログインする
2. AでリソースのID（`/api/orders/123` など）を控える
3. **Bのセッションのまま**、URLかAPIのIDをAの値に差し替えて送る
4. Aのデータが返ってきたら——それがIDORです。公開してはいけません

この「認証はするが認可しない」という穴を、脆弱→修正の実コードで最短理解したい場合は、[認証はするが認可しない：所有者スコープの落とし穴](/blog/supabase-rls-authenticated-vs-authorized-owner-scope-guide)を併読してください。

---

## 3. 類型②：Supabase RLS の設計ミス — DBが最後の砦なのに開いている

Supabase を使うなら、認可の最後の砦は**行レベルセキュリティ（RLS）**です。ここが、AI生成アプリで最も抜けます。

問題は2段階あります。**(a) そもそもポリシを張り忘れる**、**(b) 張ったが緩い**。

```sql
-- ❌ (a) RLSを有効化していない → anonキーで全行が読み書きできる
create table public.notes (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid references auth.users(id),
  body text
);
-- ここで ENABLE ROW LEVEL SECURITY を忘れると、テーブルは全公開

-- ❌ (b) 有効化はしたが「認証はするが所有者に絞らない」
create policy "read notes" on public.notes
  for select using (auth.role() = 'authenticated'); -- ログインさえしていれば全員の行が見える
```

`(b)` が特に厄介です。**ログインした人なら、他人のノートも全部読める**。これはIDORのDB版で、「認証はするが認可しない」の典型です。修正は、**所有者に行を絞る**ことです。

```sql
-- ✅ 所有者スコープを USING に入れる。書き込みは WITH CHECK も必須
alter table public.notes enable row level security;

create policy "read own notes" on public.notes
  for select using (auth.uid() = owner_id);

create policy "insert own notes" on public.notes
  for insert with check (auth.uid() = owner_id); -- WITH CHECK が無いと他人名義で挿入できる
```

これは私の空論ではなく、**公開データで確認された頻出パターン**です。公開Supabaseアプリ1,000件の一次調査で、RLSを持つアプリのうち**約9.2%が『認証はするが行を所有者に絞らない』ポリシを持っていた**（公開リポジトリ標本ゆえ下限値。所有者非スコープ＝即脆弱ではなく要確認）。この調査の手法と全数字は[Supabase RLS 実地調査（field study）](/blog/supabase-rls-security-field-study)にまとめています。

補足として、**`USING (true)` は常に悪ではありません。** そのテーブルが「誰でも読んでよい公開データ」であることを意図しているなら、それが正解です。危険なのは、**本来は所有者に絞るべき個人データのテーブルに、無意識に `true` を置いてしまう**ことです。だから「所有者非スコープ＝即脆弱」ではなく、**「意図を確認せよ」**——共有前提なのか、絞り忘れなのかを、あなたが判断する必要があります。

RLSを持たないAIビルダー製アプリで、実際に未認証の攻撃者が他人のデータを読み書きできた事例は、CVE として登録されています（[CVE-2025-48757](https://nvd.nist.gov/vuln/detail/CVE-2025-48757)、CWE-863、CVSS 9.3 CRITICAL）。「デモは動いていた」アプリが、こうなります。

---

## 4. 類型③：注入（injection）— 外部入力を「正しい前提」で扱う

AIは、フォーム・URL・APIから来る入力を**「正しい前提」で扱いがち**です。本番では、すべての外部入力を境界で検証・サニタイズしないと、インジェクションで壊れます。

代表は2つ。**SQLインジェクション**と**XSS**です。

```ts
// ❌ SQLインジェクション：ユーザー入力を文字列連結で埋め込む
const rows = await db.$queryRawUnsafe(
  `SELECT * FROM users WHERE email = '${email}'` // ' OR '1'='1 で全件返る
);

// ❌ XSS：ユーザー入力をそのままHTMLに挿す
<div dangerouslySetInnerHTML={{ __html: comment }} /> // <script> が実行される
```

修正の原則は**「連結ではなくパラメータ化」「HTMLは挿さずエスケープ」**、そして**境界での型付き検証**です。

```ts
// ✅ パラメータ化クエリ（プレースホルダにユーザー値を渡す）
const rows = await db.$queryRaw`SELECT * FROM users WHERE email = ${email}`;

// ✅ 入力は境界で Zod 検証してから使う
const Body = z.object({ email: z.string().email(), qty: z.number().int().positive() });
const { email, qty } = Body.parse(await req.json()); // 想定外の形は即座に弾く

// ✅ HTMLはそのまま挿さない。表示はテキストとして、必要ならサニタイズ後に
<div>{comment}</div> // React はデフォルトでエスケープする
```

ポイントは、**「検証を書いたつもり」で満足しないこと**。`z.string()` だけでは長さも形式も無制限です。メールならメール、数量なら正の整数、というように**意味のある制約**を境界に置いてください。

---

## 5. 類型④：秘密情報のハードコード — 鍵をドアに貼って出かける

AIは、動かすことを優先して**APIキーやトークンをコードに直書き**しがちです。そしてバイブコーディングで最も危険な取り違えが、**Supabase の `service_role` キーをクライアントに出す**ことです。

```ts
// ❌ 最悪：service_role キーをクライアント／NEXT_PUBLIC に置く
const supabase = createClient(
  "https://xxxx.supabase.co",
  process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY! // ブラウザに漏れる = 全データ露出
);
```

なぜ致命的か。**`service_role` キーは PostgreSQL の `BYPASSRLS` 権限で動き、RLSを完全に無視します**（[Supabase Docs — Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security)）。つまり、どれだけ丁寧にRLSを張っても、このキーがブラウザに出た瞬間、**RLSの有無に関係なく全テーブルが読み書き可能**になります。**守りの境界は、ポリシから「鍵の置き場所」に移る**のです。

原則はシンプルです。

| キー | 使う場所 | 性質 |
|---|---|---|
| `anon`（公開キー） | クライアント可 | RLSに従う。RLS前提で安全 |
| `service_role` | **サーバ内のみ** | RLSを無視する。絶対にクライアントへ出さない |

- `service_role` は**サーバ（Route Handler / Server Action）内だけ**で使う
- `NEXT_PUBLIC_` を付けた環境変数は**ビルドに焼き込まれ、ブラウザから見える**——秘密を絶対に付けない
- APIキー・トークンはコードに書かず env に置く（そして env はコミットしない）

また、`SECURITY DEFINER` 関数を作る場合は、**`search_path` を固定**してください（`SET search_path = ''` など）。定義者権限で動く関数は、`search_path` を汚染されるとRLSの迂回路になり得ます。そもそもその関数が本当に `SECURITY DEFINER` である必要があるかも、併せて見直す価値があります。

**自分で確かめる手順:** ビルド後の `.next` や、ブラウザの devtools の Network / Sources を開き、`service_role` を含む文字列や見覚えのある秘密が**クライアント側に出ていないか**を検索してください。1回 `grep` するだけで、多くの事故は防げます。

---

## 6. 類型⑤：検証ゲートの不在 — 直したことが、次の生成で無かったことになる

ここまでの4つを全部直したとします。**それでも、まだ足りません。** なぜなら——**次にAIがコードを生成した瞬間、同じ穴が復活するから**です。

これがバイブコーディング特有の落とし穴です。手作業のレビューで1回塞いでも、明日あなたが「この機能も追加して」とプロンプトを投げれば、AIはまた認可を省いたルートを書きます。**一度きりの目視は、AI駆動開発の速度に負けます。**

だから、**検証をCIに組み込んで、リリースのたびに機械的に再チェックされる状態**にします。生成の速さはそのまま、出荷の前に機械的なゲートを1枚挟むわけです。

```yaml
# 例：PRごとに静的解析を回し、高信頼の検出だけをビルド失敗にする
name: security-gate
on: pull_request
jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write   # SARIF を GitHub code scanning に上げる
    steps:
      - uses: actions/checkout@v4
      - run: npx @aegiskit/cli ci   # 高信頼の検出のみビルドをブロック
```

このゲート設計そのものの考え方（型・テスト・セキュリティをCIで固定する）は[AI駆動開発の品質ゲート](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security)に、AIに実装させつつ人間が仕様と検証を握る進め方は[仕様駆動でClaude Codeを本番運用に載せる](/blog/spec-driven-development-claude-code-ai-agent-production-workflow)にまとめています。要点は同じ——**AIに実装、人間に仕様決定と検証。この役割分担が、速さを安全にします。**

---

## 7. 自動化できる「水平」と、自分で確かめる「縦」

5類型を見て気づくと思います。**機械に任せられるものと、あなたにしか判断できないものがある**、と。

- **水平の統制**——ヘッダー/CSP・レート制限・入力検証・CSRF・秘密情報の混入検知。これは**アプリ横断で一律に効く**ので、ライブラリが肩代わりできます。
- **縦のリスク**——認可/IDOR・Supabase RLSの設計・テナント分離。これは**「誰が何を所有するか」というあなたの事業ルール**に依存するので、機械には最終判断ができません。

私がメンテナンスしている無料OSS **Aegis** は、この境界を誤魔化さない設計です。`npx @aegiskit/cli scan`（[/aegis](/aegis)）は、**水平の統制を自動化し、縦のリスク（認可/IDOR・RLS設計）は検出して警告する**——ここまでです。

```bash
# インストール不要。いまのプロジェクトを静的解析する
npx @aegiskit/cli scan
```

正直に言えば、**どんなツールも「完全に守る」とは言えません**。そう謳う製品はむしろ危険です（「入れたから大丈夫」という油断が最悪の結果を生む）。Aegis は RLS の設計・コードレビュー・脅威モデリングを**補完**するもので、置き換えるものではありません。クリーンな結果が意味するのは「よくある罠は踏んでいない」であって、「このコードは安全だ」ではないのです。だからこそ、**縦のリスクは最後、あなた自身の目で確かめる**必要があります。

---

## 8. 「今朝公開していい？」に、自分で答える

冒頭の問いに戻ります。**昨夜作ったSaaS、今朝公開していいか。**

答えは、次の5点を**自分の手で**通せたかどうかで決まります。

1. **認可（IDOR）** — 別アカウントのIDに差し替えて叩き、他人のデータが返らないことを確認したか
2. **RLS** — 全テーブルでRLSを有効化し、所有者スコープ（`auth.uid() = owner_id`）と `WITH CHECK` を張ったか。`USING(true)` は公開前提のテーブルだけか
3. **注入** — 生SQLをパラメータ化し、`dangerouslySetInnerHTML` を排し、境界で Zod 検証したか
4. **秘密情報** — `service_role` をクライアントに出していないか、`NEXT_PUBLIC_` に秘密を付けていないか
5. **検証ゲート** — この点検をCIに組み込み、次の生成でも自動再チェックされる状態にしたか

この5観点を、より細かい**25項目のチェックリスト**にまとめ、無料で配布しています。各項目は「読む」のではなく、**実際に手を動かして『はい』と答えられるか**を確かめる作り方です。昨夜のコードを開いたまま、上から順に通してみてください。

> **[AI生成コード セキュリティ・セルフ診断チェックリスト（Vibe Coding版）を受け取る →](/resources)**
> 認可・Supabase RLS・入力検証・秘密情報・リリース前ゲートの5観点25項目。公開前に、自分の手で。

最後にもう一度だけ。**全項目を満たしても、安全が保証されるわけではありません。** これは既知の典型的な穴を潰すための最低ラインです。扱うデータの重要度が高いなら（決済・個人情報・医療など）、そのうえで専門家によるレビューや診断も検討してください。検出された縦のリスクを実際に塞ぐ設計レビューが必要なら、私が承ります。

**速く作ることは、正しい。問題は、速く作ったものを、確かめずに公開することです。** バイブコーディングの速度は、公開前のこの5分の点検で、そのまま本番品質に変わります。
