AI生成のコードは動く。実行される。見た目も正しい。そして、ほぼすべてのプロジェクトで同じ5つのセキュリティ脆弱性を抱えて出荷される。これらは理論上のリスクではなく、明示的にセキュリティを求めていない場合、Claude、Cursor、Replit、Copilotによって生成されるほぼすべてのコードベースに現れる具体的な穴だ。
過去1年間、私は数十のバイブコーディングプロジェクトを監査してきたが、同じ5つのミスが少なくとも80%のプロジェクトに現れている。それぞれの修正には30分以下で済む。修正方法と正確な対処法を紹介する。
ミス1: フロントエンドコードにハードコードされたAPIキー
発生方法: 「Supabaseに接続して」または「Stripe支払いを追加して」とClaudeに指示すると、APIキーがReactコンポーネントに直接貼り付けられたコードが生成される。コードは完璧に動作し、シークレットキーはブラウザDevToolsを開いてネットワークタブまたはJavaScriptソースを確認すれば誰でも見ることができる。
危険な理由: Supabaseサービスロールキーはすべてのデータベースセキュリティをバイパスする。Stripeシークレットキーを使用すれば、誰かがカードを請求したり払い戻したりできる。OpenAIキーを使用すれば、誰かがあなたのアカウントで数千のAPI呼び出しを実行できる。ボットは継続的にGitHubリポジトリとデプロイされたサイトで露出したキーをスキャンしている。
修正方法: すべてのシークレットを環境変数に移動させる。Next.jsでは、NEXT_PUBLIC_で始まる変数のみがブラウザに到達する。書き込みアクセス、管理者アクセス、または財務アクセスを付与するキーはサーバー側のみである必要がある。
キーを.env.localに移動した後、コード全体で古いキー値を検索して、それらが削除されていることを確認する。その後、Gitの履歴を確認する。キーがコミットされたことがある場合、削除していても履歴に残っている。露出したキー(短時間でも)は、回転(再生成)する必要がある。
修正時間: 15~30分。
ミス2: 行レベルセキュリティなしのSupabaseテーブル
発生方法: AIはSupabaseテーブル作成クエリとCRUD操作を生成するが、行レベルセキュリティ(RLS)は有効にしない。デフォルトでは、RLSが無効になっているSupabaseテーブルは、プロジェクトURLとanonキーを持っている誰でもフルアクセスできる。これら両方は公開されている(そうあるべき)。
危険な理由: RLSがないと、ユーザーAはユーザーBのデータを照会できる。ブラウザコンソルからの簡単なfetch呼び出しですべてのテーブルのすべての行を取得できる。データベース全体が事実上公開されている。
見つけ方: Supabaseダッシュボード→テーブルエディターに移動する。「RLS Disabled」と表示されているテーブルがあれば、この問題がある。また、クライアント側のコードがservice_roleキーの代わりにanonキーを使用しているかどうかを確認する。これはさらに悪い。
修正方法: すべてのテーブルでRLSを有効にする。その後、アプリのアクセスパターンに一致するポリシーを作成する。最も一般的なポリシー: ユーザーはauth.uid() = user_idである行のSELECT、INSERT、UPDATE、DELETE のみ実行できる。1つのユーザーとしてログインし、Supabase REST APIを通じて別のユーザーのデータにアクセスしようとしてテストする。
修正時間: テーブル数に応じて30~60分。
価値を感じたか? AIツール、ワークフロー、実践的なセキュリティガイドについて、週に1つの深掘り記事を公開している。最初に読む人に参加する →
ミス3: フォームまたはAPIルートの入力検証なし
発生方法: AIは任意の入力を受け入れるフォームと、入力されたデータを信頼するAPIルートを生成する。フォームはHTML属性「required」を持っているが、サーバー側の検証がない。誰かがフォームを完全にバイパスして、直接APIリクエストを送信し、任意のペイロードを含める。
危険な理由: サーバー側の検証がないと、攻撃者はスクリプトをデータベースに注入(保存型XSS)したり、サーバーをクラッシュさせるオーバーサイズペイロードを送信したり、負の価格やSQLを含むメールフィールドのようなアプリのロジックを破損するデータを送信したりできる。
見つけ方: コードベースのAPIルートまたはサーバーアクションを開く。req.bodyまたはフォームデータをすべてのフィールドの形状、タイプ、長さを確認せずに使用している場合、この問題がある。
修正方法: スキーマ検証ライブラリを使用してすべてのエンドポイントにサーバー側の検証を追加する。Zodはスクリプトプロジェクトの標準:
import { z } from 'zod';
const taskSchema = z.object({
title: z.string().min(1).max(200),
description: z.string().max(2000).optional(),
priority: z.enum(['low', 'medium', 'high']),
});
// APIルートで:
const parsed = taskSchema.safeParse(req.body);
if (!parsed.success) {
return Response.json({ error: parsed.error }, { status: 400 });
}
HTMLとしてレンダリングされるフィールド(コメント、説明、自己紹介)については、表示する前にDOMPurifyで出力をサニタイズする。
修正時間: 30~60分。
ミス4: APIエンドポイントでのレート制限なし
発生方法: AIは明示的に要求されない限りレート制限を追加しない。アプリが公開するすべてのAPIルートは、スクリプトを持っている誰でも1秒あたり数千回ヒットできる。
危険な理由: レート制限がないと、誰かがログインエンドポイントをブルートフォースしたり、リストエンドポイントを繰り返しヒットしてデータベースをスクレイプしたり、サーバーをオーバーフロー(サービス拒否)させたり、OpenAIやStripeのような外部サービスを呼び出すエンドポイントを持つ場合、APIクォータを消費したりできる。
見つけ方: APIルートのいずれかが単一のIPまたはユーザーが最近行った要求数を確認していない場合、レート制限がない。
修正方法: Vercelホスト型アプリの最も簡単なアプローチは、開発用のインメモリレート制限器と本番環境用のRedisベースの制限器である。VercelのEdge Middlewareは基本的なレート制限を処理できる。Upstash Redis(フリーティア)と@upstash/ratelimitを使用すると、少量のコードで本番グレードのレート制限が得られる。
合理的な出発点: 認証されたユーザーには1分あたり60リクエスト、認証されていないユーザーには1分あたり20、ログイン/サインアップエンドポイントには1分あたり5(ブルートフォース防止)。
修正時間: 20~45分。
ミス5: 保護されたページとAPIの認証チェックなし
発生方法: AIは美しいダッシュボードページとユーザーデータを返すAPIルートを生成するが、ユーザーが実際にログインしていることを確認するミドルウェアを追加しない。開発中は常にログインしているため、ページは「動作する」。本番環境では、誰かがログインせずにダッシュボードURLに直接アクセスしたり、APIエンドポイントをヒットしてデータを取得したりできる。
危険な理由: 保護されたページへの認証なしアクセスは、URLを推測するだけでユーザーダッシュボード、管理パネル、または非公開データを見ることができることを意味する。保護されていないAPIルートは、Postmanやcurlのようなツールが認証情報なしでデータを取得できることを意味する。
見つけ方: シークレットブラウザウィンドウ(ログインしていない)を開いて、ダッシュボード、設定、または管理URLに直接移動する。ログインページにリダイレクトされずにコンテンツが表示される場合、この問題がある。その後、APIルートを直接ヒットしてみる。有効なセッションクッキーまたはauthトークンなしでデータを返す場合、それらは保護されていない。
修正方法: すべての保護されたルートの前に実行される認証ミドルウェアを追加する。Next.js App Routerで、プロジェクトルートにmiddleware.tsを作成:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('session');
if (!session) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/protected/:path*'],
};
マッチャーを調整して、認証が必要なすべてのルートをカバーする。実装後、シークレットモードでテストする。
修正時間: 15~30分。
今後のプロジェクトでこれらを防ぐ方法
パターンは明確だ: AIは機能的に動作するが、セキュリティを求めていないため、セキュリティをスキップするコードを生成する。修正は、それを最初に求めることだ。
バイブコーディングプロジェクトの初期プロンプトの最後に以下を追加する:
SECURITY REQUIREMENTS:
- すべてのシークレットを環境変数で(ハードコードしない)
- すべてのテーブルでSupabase RLSを有効にしてユーザーごとのポリシーを設定
- すべてのAPIルートでサーバー側の入力検証(Zodを使用)
- すべてのパブリックエンドポイントでのレート制限
- すべての保護されたルートの認証ミドルウェア
- クライアント側のコードにサービスロールキーを使用しない
これがすべてをキャッチするわけではありませんが、後で必要なクリーンアップの代わりに、最初のパスで5つの最も一般的な脆弱性を排除します。
最初からAIからより安全なコードを生成するプロンプトの書き方をより上手にしたい場合、無料のプロンプトオプティマイザーが指示を構造化するのに役立つ。完全なセキュリティのウォークスルーについては、ステップバイステップガイドを参照: クライアントに提供する前にバイブコーディングされたアプリを保護する方法。
これは毎週やることだ。 AIツール、ワークフロー、率直な意見についての1つの深掘り記事。ハイプなし、フィラーなし。参加する →
開示:この記事のいくつかのリンクはアフィリエイトリンクです。個人的にテストして定期的に使用しているツールのみを推奨している。完全な開示ポリシーを参照してください。