v0で作ったプロトタイプを企業DBに直接接続してはいけない理由と safeな接続法
TuBrief Editorial
July 23, 2026
0
Computing/SoftwareWritten with AI assistance from the source video. The video is the authority.
More from the community
Comments (0)
Log in to leave a comment
No posts yet
Written with AI assistance from the source video. The video is the authority.
Log in to leave a comment
No posts yet
非エンジニアの社員がv0やCursorのようなバイブコーディング(Vibe Coding)ツールを使って、わずか3日で立派なWebアプリを作ってきます。社長は大喜びで「今すぐ本番DBに繋いで顧客に使ってもらおう」と言い出すわけです。このとき、開発陣やCTOは冷や汗をかきます。AIが生成したコードを開いてみると、クライアントコンポーネントの真ん中にDB接続文字列(DSN)がそのまま埋め込まれていたり、なんの検証もなくSQLクエリを投げている状態だからです。
これをそのまま運用サーバーに上げたら、事故が起きるのは時間の問題です。OWASP Top 10 for LLM Applications 2025レポートでも、AI生成コードの過剰な権限付与(LLM06)と入力値の未検証(LLM05)が最も危険なセキュリティ要因として挙げられています。実際にWebアプリケーションのセキュリティ侵害事故の73%が、入力検証の不足を狙ったものです。
非エンジニアの生産性を削ぐことなく、会社のDBを守るためには、プロトタイプとDBの間に堅牢な統制レイヤーを配置する必要があります。
まず最初に行うべきは、非エンジニアが作成したアプリがDBを直接呼び出せないよう、経路を塞ぐことです。Next.js App Routerのサーバールートハンドラーをプロキシとして配置し、中間でZodライブラリを用いて入ってくるデータをリアルタイムで検証する必要があります。TypeScriptの型チェックは開発時にのみ動作するものであり、実際のユーザーが異常なデータを送信した際にサーバーが落ちるのを防ぐことはできません。
| 検証項目 | ブラウザUI側の検証 | サーバーサイドZodプロキシレイヤー |
|---|---|---|
| 実行場所 | 顧客のブラウザ | Vercel Serverless Runtime |
| セキュリティ性 | 開発者ツールで容易に回避可能 | サーバーで強制遮断してDBを保護 |
| データ検証 | 形式的なテキストチェック | 実行時の値の範囲および型を精密検証 |
| 失敗時の処理 | 画面に警告文を表示 | HTTP 400返却およびSentryへエラー記録 |
構築の手順はシンプルです。
app/api/v1/customer-records/route.ts ファイルを作成します。companyName は最低2文字以上、 contactEmail はメールアドレス形式、 employeeCount は正の整数のみを受け付けるよう仕様を組みます。request.json() で入ってきたリクエストを schema.parse() で検査します。通過したデータのみをPrisma ORM経由でDBに挿入し、失敗した場合はその場で400エラーを返して弾きます。アプリ側の検証と同時に、DB自体にも防壁を張る必要があります。PostgreSQLベースのSupabaseを使用している場合は、Row Level Security (RLS) の設定が最適解です。ユーザーの役割(ロール)情報を、ユーザーが操作可能な raw_user_meta_data ではなく、管理者のみが操作する raw_app_meta_data に保持し、JWTを検証します。
`sql
-- 1. テーブルのRLSを有効化
ALTER TABLE public.enterprise_documents ENABLE ROW LEVEL SECURITY;
-- 2. JWTからユーザーロールを抽出する関数を作成
CREATE OR REPLACE FUNCTION get_user_role()
RETURNS text AS
LANGUAGE sql STABLE;
-- 3. 部署が同じか、管理者の場合のみ参照を許可するポリシー
CREATE POLICY "Department Access Policy" ON public.enterprise_documents
FOR SELECT USING (
get_user_role() = 'admin' OR
department = (current_setting('request.jwt.claims', true)::json->'app_metadata'->>'department')
);
`
この作業を行っておけば、非エンジニアが誤ってサービスロールキー(Service Role Key)をコードに漏洩させてしまったとしても、他部署の機密文書が流出するような事態は絶対に防げます。
AIツールはまず画面を表示させることに集中するよう設計されているため、APIキーやDB接続文字列をブラウザに露出する場所に平気で記述します。最もよく見られるミスは、 NEXT_PUBLIC_ プレフィックスをむやみに付けることです。Next.jsにおいて、このプレフィックスが付いた変数BrowserのJavaScriptファイル内にプレーンテキストとして組み込まれます。 NEXT_PUBLIC_OPENAI_API_KEY のような変数を作成した瞬間、F12開発者ツールを開く知識がある人なら誰でもAPIキーを取得して自由に使うことができるようになってしまいます。
2026年4月にVercelインフラで発生したセキュリティ侵害事故を見ても明らかです。連携していたサードパーティAIツールの権限が突破されたことで、隠されていなかった環境変数が露出し、多くのチームが徹夜でDBパスワードやAPIキーを個別に再発行する羽目になりました。
このようなミスを目視で一つひとつ検品するのは不可能です。GitHub ActionsにGitleaks静的解析ツールを組み込み、コードをマージする前に自動で検出・排除する必要があります。
`yaml
name: Security Scan
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
gitleaks-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install and Run Gitleaks
run: |
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.0/gitleaks_8.18.0_linux_x64.tar.gz
tar -xzvf gitleaks_8.18.0_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/
gitleaks detect --source=. --verbose --redact --exit-code=1
public-env-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check Unsafe Public Keys
run: |
UNSAFE=(grep -rn "NEXT_PUBLIC_.*\(SECRET\|KEY\|PASSWORD\|TOKEN\|DSN\)" . || true)
if [ -n "UNSAFE" ]; then
echo "クライアントに露出した機密変数を発見:"
echo "$UNSAFE"
exit 1
fi
`
このパイプラインをセットアップしておけば、非エンジニアがAPIキーをソースコードや NEXT_PUBLIC_ 変数に入れてPushした瞬間に、デプロイが自動的にブロックされます。
Vercelダッシュボードに変数を登録する際も、Development、Preview、Productionのスコープを明確に分ける必要があります。特に Sensitive Environment Variable チェックボックスは必ずオンにしてください。ビルドログに出力されず、ダッシュボード上でも平文のコピーができないよう暗号化されます。
OpenAIやAnthropicのサーバーが停止したりレスポンスが遅延したりすると、サーバーレスハンドラー全体が待機状態に陥り、連鎖障害が発生します。このような場合は、Node.jsの opossum ライ브ラリを使ってサーキットブレーカーを設定する必要があります。
`typescript
import CircuitBreaker from 'opossum';
async function callLLM(prompt: string) {
// LLM API呼び出しロジック
}
const options = {
timeout: 5000, // 5秒を超えたら失敗処理
errorThresholdPercentage: 50, // 失敗率が50%を超えたらブレーカーオープン
resetTimeout: 30000 // 30秒後に再試行
};
const breaker = new CircuitBreaker(callLLM, options);
breaker.fallback(() => ({ error: "AIの応答が遅延しています。しばらく時間をおいてから再試行してください。" }));
`
APIの応答が遅れた際に、0.1秒であらかじめ用意した案内文を返すことで、サービス全体がダウンするのを防ぐことができます。
障害状況を非エンジニアの作成者がすぐに把握できるよう、SentryとSlack Webhookも連携しておきます。
`typescript
// app/api/webhooks/sentry-to-slack/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const event = await request.json();
const webhookUrl = process.env.SLACK_INCOMING_WEBHOOK_URL;
if (!webhookUrl) return NextResponse.json({ error: 'Webhookなし' }, { status: 500 });
const title = event.data?.issue?.title || '不明なシステムエラー';
const issueUrl = event.data?.issue?.permalink || '#';
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: '🚨 プロダクションエラー発生' }
},
{
type: 'section',
text: { type: 'mrkdwn', text: `*エラー内容:*
${title}` }
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'Sentryレポートを見る' },
url: issueUrl,
style: 'danger'
}
]
}
]
})
});
return NextResponse.json({ success: true });
} catch (err) {
return NextResponse.json({ error: 'Webhook失敗' }, { status: 500 });
}
}
`
エラーが拡大してメインページが表示されないレベルのP1障害が発生した場合でも、エンジニアの対応を待つ必要はありません。Vercelは過去のデプロイメントをそのまま保存しているため、数クリックで直前の正常な状態に戻すことができます。
わずか30秒待つだけで、ドメインのルーティングが以前のバージョンに切り替わります。Zod検証、Gitleaksスキャン、Vercelのロールバック手順まで整えておけば、非エンジニアに思いむくままアプリを作らせつつも、夜安心して眠ることができます。