AIのあとしまつ

実務・技術解説

AI生成コードのセキュリティリスク10選——「動くからヨシ」が招く本番障害

AIが生成したコードに潜むセキュリティリスクを10個に整理。環境変数の直書き、RLS未設定、認証の穴、XSSなど、本番公開前に必ず確認すべきポイントを具体的なコード例と一緒に解説。

AIコード セキュリティAI生成コード リスクXSS 対策SQLインジェクション 防止AI生成コード 品質Supabase RLSAI コーディング セキュリティ環境変数 セキュリティWebアプリ 脆弱性本番 セキュリティ

「動くからヨシ」のまま本番に出すと、何が起きるか。

AIが生成するコードは「機能を動かすこと」を最優先する。セキュリティの安全装置——バリデーション、認証チェック、環境変数の分離——は、指示しない限り省略されやすい。

実際に起きたことを一つ挙げると:Bolt.newで作ったアプリをそのまま公開したら、Supabaseの全テーブルが誰でも閲覧可能な状態だった。RLSが無効のままだった。

これはツールの問題ではなく、AIが「まず動くものを作る」という設計思想で動いているからだ。以下の10個のリスクは、AI生成コードで特に見落とされやすいものだ。本番公開前に確認してほしい。

数字で見るとその深刻さがわかる。Wizの2025年調査によると、AI生成コードの45%にセキュリティ脆弱性が含まれている。言語別に見ると、JavaのAI生成コードでは10個中7個(40〜70%)に脆弱性が見つかり、JavaScript・Python・C#でも約40%の脆弱性率が報告されている。さらに、GitHub Copilotが生成したコードの40%に「最も危険なソフトウェアの弱点Top 25(OWASP)」が含まれていたという結果もある。「AIが書いたから安全」ではなく、「AIが書いたから確認が必要」と考えてほしい。

リスク1: APIキーが直書きされている

一番多くて、一番危険なパターンだ。

// AI生成コードでよく見る
const openai = new OpenAI({
  apiKey: "sk-proj-xxxxxx" // これがGitHubに上がる
})

const stripe = new Stripe('sk_live_xxxxxx') // これも

AIは「すぐ動くコード」を優先するため、開発中のハードコードをそのまま残す。これをコミットしてGitHubにプッシュすると、APIキーが全世界に公開される。Stripeのシークレットキーが漏洩した場合、不正な決済が発生する可能性がある。

実際に起きた事例を挙げると:2026年2月、AIエージェント専用SNS「Moltbook」で約150万件のAPIキーが外部から閲覧可能な状態で流出した(詳細はAIスタートアップの設定ミス型情報漏洩を参照)。OpenAI APIキーなど第三者サービスの認証情報も含まれており、エージェントの乗っ取りや不正利用が可能な状態だった。同様に、中国AIスタートアップDeepSeekでも認証なしのデータベースが公開され、100万件超のAPIシークレットが流出している。「動くから大丈夫」の判断が、こうした規模の被害につながる。

**確認方法:**コミット前に git diff | grep -i "sk-\|secret\|password\|api_key" で検索する。すべての認証情報は .env ファイルに入れて .gitignore に追加する。APIキー流出による高額請求の防ぎ方はクラウド破産を防ぐで詳しく解説している。

リスク2: SupabaseのRLSが無効または甘い

Supabaseを使ったアプリで最も多い問題だ。

RLS(Row Level Security)が無効なテーブルは、Supabaseの匿名キー(フロントエンドのコードに含まれている)を持つ誰でも全データにアクセスできる。ブラウザのコンソールから数行のJavaScriptで、全ユーザーのデータが取得できてしまう。

前述のMoltbookのインシデントは、まさにこの設定ミスが原因だ。「認証なしで誰でもアクセスできる状態のデータベースが公開されていた」と報告されており、3.5万件超のメールアドレスとプライベートメッセージも同時に流出している。

-- AIがよく設定する「全許可」ポリシー(開発中は便利だが危険)
CREATE POLICY "Allow all" ON users
  FOR ALL USING (true);

-- 本番で必要なポリシー
CREATE POLICY "Users can read own data" ON users
  FOR SELECT USING (auth.uid() = id);

→ RLSの詳細はSupabase RLS設定ガイド

→ 自分のアプリのデータが外から見える状態になっていないかチェックするならSupabaseセキュリティ診断

リスク3: 認証チェックのないAPIエンドポイント

「ユーザー管理画面を作って」と指示すると、UIは作られるが、APIレベルでの認証チェックが抜けることがある。URLを直接叩けばデータが取れてしまう状態だ。

// よくある生成パターン(認証チェックがない)
export async function GET(request) {
  const users = await db.query('SELECT * FROM users')
  return Response.json(users)
}

// 必要なパターン
export async function GET(request) {
  const session = await getSession(request)
  if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })

  const users = await db.query('SELECT * FROM users WHERE org_id = ?', [session.orgId])
  return Response.json(users)
}

すべてのAPIエンドポイントで「このリクエストは誰が送っているか」を確認する必要がある。

リスク4: バリデーションがフロントエンドだけ

フォームのバリデーションを実装すると、ブラウザ側だけに実装されることがある。

ブラウザのバリデーションは簡単にバイパスできる。curl コマンドやAPIクライアントから直接リクエストを送れば、どんなデータでもデータベースに入る。文字数無制限の入力、負の金額、不正なメールアドレスなど。

確認方法: すべてのフォームに対応するAPIルートで、サーバーサイドのバリデーションがあるか確認する。Zodなどのスキーマバリデーションライブラリを使うと漏れが減る。

リスク5: SQLインジェクションが刺さる

ORMを使わずに生のSQLを書くとき、AIが文字列連結でクエリを組み立てることがある。

// 危険
const query = `SELECT * FROM users WHERE email = '${email}'`

// email に ' OR '1'='1 を入力されると全データが取れる

// 安全
const { data } = await supabase.from('users').select().eq('email', email)

SupabaseやPrismaなどのライブラリを使っていれば自動で対処されるが、生のSQL文字列連結が残っていないか確認する。

リスク6: dangerouslySetInnerHTMLの不用意な使用

ReactのXSS対策は強力だが、dangerouslySetInnerHTML を使うとその保護を外せてしまう。AIがリッチテキストや動的HTMLを表示するときにこれを使うことがある。

// 危険:ユーザー入力をそのまま表示
<div dangerouslySetInnerHTML={{ __html: userContent }} />

// 安全:サニタイズしてから表示
import DOMPurify from 'dompurify'
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />

dangerouslySetInnerHTML が使われている箇所を検索して、ユーザー入力が入る場合はサニタイズライブラリを適用する。

リスク7: エラーメッセージが詳細すぎる

エラー時にスタックトレースやデータベースの構造情報がそのままユーザーに返ることがある。

// 危険
return Response.json({
  error: error.message,  // "Connection to postgres://user:password@host:5432/db failed"
  stack: error.stack
})

// 安全
console.error(error) // ログには詳細を記録
return Response.json({ error: '処理に失敗しました' }, { status: 500 })

本番環境ではユーザーに見せるエラーと、ログに記録するエラーを分ける。

リスク8: 依存パッケージの脆弱性

AIが生成するコードには古いバージョンのライブラリが混入することがある。既知の脆弱性(CVE)が含まれているパッケージをそのまま本番で動かすことになる。

# 確認方法
npm audit

# 修正
npm audit fix

重大度が「high」「critical」のものは本番公開前に対処する。

リスク9: CORSの設定が甘い

Access-Control-Allow-Origin: *(全オリジン許可)のままになっていることがある。これは悪意のあるサイトからのクロスオリジンリクエストを許可してしまう。

本番環境では許可するオリジンを明示的に指定する:

// 危険
headers.set('Access-Control-Allow-Origin', '*')

// 安全
const allowedOrigins = ['https://yourdomain.com', 'https://app.yourdomain.com']
const origin = request.headers.get('origin')
if (allowedOrigins.includes(origin)) {
  headers.set('Access-Control-Allow-Origin', origin)
}

リスク10: ログインのレート制限がない

パスワードを総当たりで試す攻撃(ブルートフォース)への対処がない。AIは「ログイン機能」を作ると、試行回数の制限を入れないことが多い。

Supabase Authを使っている場合は、Auth設定でRate Limitingを確認する。カスタムの認証実装では、失敗カウントと一時ロックの仕組みを追加する必要がある。

リスク11: レートリミットが設定されていない

認証APIや重要なエンドポイントにレートリミットがないと、ブルートフォース攻撃(パスワードを総当たりで試す)が可能になる。AIは「とりあえず動く」実装をするため、レートリミットは省略されやすい。

// ダメなパターン: 回数制限なし
app.post('/api/login', async (req, res) => {
  const user = await validateCredentials(req.body)
  // 何回でも試せる
})

// 対策: upstash/rateLimitなどでAPIごとに制限
import { Ratelimit } from "@upstash/ratelimit"
const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"), // 10秒に10回まで
})

確認方法:ログインAPIに1秒間に100回リクエストを送っても全部通るか試す。Supabase Authを使っている場合はAuthの設定でレートリミットを確認する。

リスク12: セッション管理が不適切

ログアウト後もセッショントークンが有効なままになっているケースがある。AIは「ログイン」の実装に集中し、「ログアウト後の無効化」を省略することが多い。

// ダメなパターン: フロントエンドでだけトークンを削除
const logout = () => {
  localStorage.removeItem('token') // フロントのみ。サーバー側は有効なまま
}

// 正しい対策: サーバー側でもセッションを無効化
const logout = async () => {
  await supabase.auth.signOut() // サーバー側でもセッション破棄
  router.push('/login')
}

確認方法:ログアウト後、開発者ツールのNetworkタブで認証が必要なAPIを直接叩く。401が返れば正しい。200が返ったら問題あり。


本番公開前のセキュリティチェックリスト

[ ] APIキー・シークレットが環境変数に分離されているか
[ ] .env が .gitignore に含まれているか
[ ] Supabaseの全テーブルでRLSが有効か
[ ] RLSポリシーがauth.uid()で制限されているか
[ ] すべてのAPIに認証チェックがあるか
[ ] バリデーションがサーバーサイドにもあるか
[ ] dangerouslySetInnerHTML の使用箇所を確認したか
[ ] npm audit で重大な脆弱性がないか
[ ] 本番のエラーメッセージが詳細を含まないか
[ ] CORSの許可オリジンが限定されているか

このチェックリストは最低限の確認事項だ。より体系的な対策はVibe Codingで最低限やるべきセキュリティ対策にまとめている。ユーザーの個人情報を扱う、決済機能がある、法人向けに提供するケースでは、より本格的なセキュリティ診断が必要になる。

AIのあとしまつでは、AI生成コードのセキュリティ診断と修正を本番化サポートに含めている。「これで本番に出して大丈夫か」という段階で相談してほしい。

よくある質問

「AIに指示すれば直してくれるのでは?」

部分的には直せる。ただしAIは「指摘された問題」は修正できるが、「まだ発見されていない問題」は気づかない。このリスク10選を一通りAIにレビューさせることはできるが、それで全部見つかる保証はない。

「小さなサービスだからセキュリティは後でいい?」

規模は関係ない。攻撃は自動化されており、脆弱なサービスを機械的にスキャンして見つけてくる。公開した瞬間から対象になる。

「どれから先に対応すればいい?」

優先順位は: (1) APIキー漏洩確認、(2) RLS設定、(3) 認証テスト、の順。この3つが最も被害が大きく、最も見落とされやすい。

「全部自分でチェックできますか?」

チェックリストを使えば表面的な確認はできる。ただし「問題がないこと」の確認には専門知識が必要。特に認証フローの脆弱性、SQLインジェクションの可能性、XSSの抜け漏れは、知識がないと見落としやすい。

あわせて読みたい