背景
バイブコーディング(Lovable, Bolt, Claude Codeなどで自然言語からアプリを作る開発スタイル)で作られたアプリに、実際どれくらいセキュリティ上の問題があるのか気になった。CVE-2025-48757では、Supabaseの設定不備が原因で170を超えるアプリに影響が出たという報告もある。
実際に手を動かして確かめてみることにした。
対象データ
awesome-vibe-coded-apps というリポジトリが、5,868件のライブ稼働中アプリ一覧を data/projects.json として公開している。各エントリには detected_stack(検出された技術スタック)というフィールドがあり、SupabaseまたはFirebaseを含み、かつlive_check_statusがliveのものだけを対象にした(約200件)。
{
"slug": "example-app",
"live_url": "https://example.com/",
"detected_stack": ["Cloudflare", "Next.js", "Supabase"],
"live_check_status": "live"
}
検査範囲を「完全パッシブ」に限定した理由
一番検証したかったのは「Supabaseの匿名キーを使って実際にテーブルを読み取れるか(RLSが機能しているか)」だった。しかし、これは見ず知らずの他人の本番システムに対する無許可の読み取りクエリになる。技術的には可能でも、不正アクセス禁止法等のグレーゾーンに触れうると判断し、やらないことにした。
代わりに、サーバーが誰にでも配信している内容を読むだけの検査に絞った。
- トップページHTML・読み込まれるJSバンドル内のシークレット文字列パターン(Stripe live key, AWSアクセスキー, OpenAI/Anthropic APIキー, DB接続文字列等)
- Supabaseの匿名キー/service_roleキーの検出。JWT形式の文字列を見つけて base64url でペイロード部分をデコードし、roleクレームで判定する
function decodeJwtPayload(token) {
try {
const parts = token.split(".");
const json = Buffer.from(parts[1], "base64url").toString("utf8");
return JSON.parse(json);
} catch {
return null;
}
}
// payload.role === "service_role" なら要注意
- /.env, /.git/config, /.git/HEAD が直接web公開されていないか(単純なGETリクエストのみ。SPAのフォールバックHTMLと本物のファイル内容を区別するヒューリスティックを入れた)
Node.js(v22の標準fetchのみ、外部ライブラリなし)で実装し、Vite系アプリはビルド時に環境変数がJSバンドルへ literal に埋め込まれることが多いため、ヘッドレスブラウザなしで足りた。
結果
200件中、シークレットが直接露出していたのは2件(約1%)。内訳はSlackのwebhook URLが1件、Stripeの制限付きキーが1件。service_roleキーの露出・.env/.gitの直接公開は0件だった。
考察: 低い数字は「安全」を意味しない
直感的には低い数字に見えるが、これはむしろ懸念材料だと考えている。今回の検査は意図的にRLS周りを除外しており、実際に多発しているのはまさにそこ(CVE-2025-48757、DeepStrikeの大規模Supabase誤設定調査など)。つまり「見てすぐ分かる場所」に問題は少ないが、「見ようとしないと分からない場所」の実態は今回測れていない、というのが正確な結論になる。
おまけ
この調査をきっかけに、GitHub連携で自分のプロジェクトの状態を非エンジニア向けに翻訳して見せる個人開発ツール「Sodachi」を作った。興味があれば覗いてみてほしい。