0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI生成コードのセキュリティ違反を44%から19%に削減した方法【バリデーション0%→100%の実測記録】

0
Last updated at Posted at 2026-04-05

正直に言います。自分が最初に AI でコードを生成したとき、入力バリデーションが全くない状態のまま使おうとしていました。

「とりあえず動くから OK」という気持ちで Claude に「認証機能を作って」と頼んで、出てきたコードをほぼそのままコピペ。セキュリティのことなんて後回しでした。

そしてある日、出力されたコードをよく読んだら、パスワードが平文のまま保存されていたことに気づきました。

// これが本当に出てきたコード(実話)
const user = { id: crypto.randomUUID(), email, password };
users.push(user);

password をそのまま配列に突っ込んでいます。ハッシュ化も何もなし。もしこのコードを本番に持っていったら……と思うとゾッとしました。

「AI のコードはなんとなく不安」、その感覚は正しい

この記事を読んでいる方の中にも、こんな経験はありませんか?

  • AI が生成したコードをレビューしても、「これ大丈夫?」という漠然とした不安がある
  • セキュリティの知識が浅くて、どこが危ないのか自分では判断できない
  • ガイドラインを整備しようとしたけど、効果があるのかよくわからない

自分もずっとそうでした。「たぶん大丈夫だろう」で進めてきた部分が、実はかなりリスクのある状態だったということを、今回の検証で数字として突きつけられました。

結論から言います

ガイドラインを AI に渡すだけで、セキュリティ関連チェック項目(C1〜C10)の違反率が 44%から19%に削減されました。特に入力バリデーション(Zod)の実装率は 0%→100%(15試行全て)という結果です。

テスト Before After(ガイドライン導入) After+Check(自動チェック追加)
認証ミドルウェア 54点 78点 78点
CRUD API 58点 82点 84点
UI コンポーネント 56点 80点 80点
全体平均 56点 80点 81点

スコアは 10 項目のチェックリスト(命名・セキュリティ・エラーハンドリング・型安全性・ディレクトリ構成・API設計など)を 100 点換算したものです。

「56点 → 80点」というスコアの上がり方より、自分が重視しているのはセキュリティ違反の件数が劇的に減ったという事実です。特に「入力バリデーション(Zod)の実装率が 0% → 100% になった」というのは、驚きと同時に、Before がいかにひどかったかを物語っています。

この検証を通じて何をしたか、どう変わったかを、できるだけ具体的に書いていきます。

導入にかかる時間

最小構成であれば、5分〜10分 で導入できます。具体的な手順は前回の記事「【30分】Next.jsプロジェクトにAIコーディングのガイドラインを導入する手順(Before/Afterコード比較付き)」にまとめていますので、合わせてご覧ください。

何を・どうやって検証したか

使ったのは自分が作った OSS のガイドラインセットです。以前の記事で紹介したので、詳細はそちらを。

3種類のプロンプト × 5回 = 15 試行

実際の開発でよく出てくる 3 つのシナリオを選びました。

  • プロンプト A(認証ミドルウェア): JWT(JSON Web Token)認証 + /api/auth/login・/api/auth/register の実装 + /dashboard のルート保護
  • プロンプト B(CRUD API): タスク管理の GET / POST / PUT / DELETE + Drizzle ORM(DB 操作ライブラリ)との接続
  • プロンプト C(UI コンポーネント): タスク一覧の Server Component(サーバー側で描画するコンポーネント)+ フォームの Client Component(ブラウザ側で動くコンポーネント)

各プロンプトを 5 回ずつ実行して、合計 15 個のコードセットを生成。それを同じチェックリストで評価しました。導入作業は最小構成で 5〜10 分ほどです。

3 パターンで比較

  • Before: ガイドラインなし。普通に「〇〇を作って」と頼むだけ
  • After: ガイドラインを渡した状態で同じプロンプトを実行
  • After+Check: After に加えて /ai-dev-os-check(以下 check ツール)コマンドで自動チェック・修正も実施(詳しくは こちら)

全体の結果

特に目立つのは以下の 3 点です。

項目 Before After+Check
Zod バリデーション(C3) 0%(15試行全滅) 100%(完全解消)
CSRF / 認証チェック(C4) 20% 80%以上
ディレクトリ構成(C8) 80% 100%

とりわけ「Zod バリデーション 0%」というのは衝撃的でした。15 試行全てで、入力バリデーションが実装されていなかったのです。

実測値の内訳:C1〜C10 の全チェック項目(各試行10項目 × 15試行 = 150件)で違反率を集計すると、Before 44% → After+Check 19%(▲25ポイント)です。特にインパクトが大きかったのは C3(Zod バリデーション)で、15試行全てで未実装(0%)だったものがガイドライン導入後は全試行で実装(100%)になりました。C4(CSRF/認証チェック)も 20% → 80%以上に改善。残存する19%の違反は主に「どの Auth 基盤を使うか」といったアーキテクチャ決定が必要な項目です(後述)。

何が変わったか — Before/After のコードで見る

変化1: 入力バリデーション

「入力バリデーション」は、セキュリティの基本中の基本です。メールアドレスの形式チェック、パスワードの最小文字数、不正な文字の除外など。これがないと、攻撃者が意図的におかしな値を送ってきたときにシステムが正しく動かなくなります。

Before(認証ミドルウェアのログイン API)

// Before: 手動チェックのみ、型・形式バリデーションなし
export async function POST(request: NextRequest) {
  const { email, password } = await request.json();

  if (!email || !password) {
    return NextResponse.json(
      { error: "Email and password are required" },
      { status: 400 }
    );
  }
}

「email と password が空でないか」しか確認していません。email に "not_an_email" を渡しても素通りします。

After(ガイドライン導入後)

// After: Zod スキーマで型・形式・長さを一括バリデーション
import { loginSchema } from "@/features/auth/schema";

export async function POST(req: NextRequest) {
  const body = await req.json().catch(() => null);
  if (!body) {
    return NextResponse.json({ error: "リクエストボディが不正です" }, { status: 400 });
  }

  const parsed = loginSchema.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json(
      { error: "入力値が不正です", details: parsed.error.flatten().fieldErrors },
      { status: 422 }
    );
  }
}

変わったポイントは 3 つです。

  • Zod(TypeScript 用のバリデーションライブラリ)によって email の形式チェック + パスワードの最小文字数チェックが自動化
  • request.json() のパースエラー(JSON が壊れていた場合)を明示的に処理
  • バリデーション失敗時のレスポンスに どのフィールドが何の理由で失敗したか(fieldErrors)が含まれる

変化2: パスワードの保管方法

冒頭の「平文パスワード保存」の話です。これは絶対にやってはいけない実装のひとつで、もし DB が漏洩したとき、ユーザーのパスワードが全件そのまま読まれます。

認証ミドルウェアのテスト(プロンプトA)では、 5回の試行中4回(80%) で以下のような平文保存コードが生成されていました。

Before(平文保存)

// パスワードをそのままオブジェクトに格納
const user = { id: crypto.randomUUID(), email, password };
users.push(user);

After(ガイドライン導入後)

import bcrypt from "bcryptjs";

export async function createUser(email: string, password: string): Promise<User> {
  const hashedPassword = await bcrypt.hash(password, 12);
  const user: User = { id: randomUUID(), email, hashedPassword };
  users.set(email, user);
  return user;
}

bcrypt(パスワードをハッシュ化するためのライブラリ)でハッシュ化してから保存するようになりました。12 はハッシュ化の強度を表すコスト値で、一般的に推奨される値です。

Before と After を並べると「なぜこんな当たり前のことを Before はやっていなかったのか」と思いますよね。ガイドラインなしだと、AI はそこまで気を配らずに「動くコード」を出力してくる場合があります。逆に言えば、ガイドラインに「パスワードは必ず bcrypt でハッシュ化すること」と書いてあれば、ちゃんとそう書いてきます。

変化3: CRUD API の入力バリデーション

API だけでなく、CRUD 処理でも同様の問題が出ます。

Before(タスク作成 API)

// status に "invalid_value" が渡っても INSERT される
export async function POST(request: NextRequest) {
  const body = await request.json();
  const { title, description, status, dueDate } = body;
  if (!title) {
    return NextResponse.json({ error: "title is required" }, { status: 400 });
  }
  const [task] = await db.insert(tasks).values({ title, description, status, dueDate }).returning();
}

status に "invalid_value" が渡っても DB に INSERT されてしまいます。

After(ガイドライン導入後)

// Zod で型・enum・長さ・日付形式を一括バリデーション
const parsed = CreateTaskSchema.safeParse(body);
if (!parsed.success) {
  return NextResponse.json(
    { error: { code: "VALIDATION_ERROR", message: parsed.error.flatten() } },
    { status: 422 }
  );
}
// status は ["todo","in_progress","done"] に限定
// dueDate は ISO datetime 形式 + 未来日時チェック

status は ["todo","in_progress","done"] の enum に限定。dueDate は ISO 形式の日時かつ未来日時のみ受け付けるよう、Zod のスキーマで宣言的に書けるようになります。

チェックツールが直したもの(C1〜C10の外側の修正)

C1〜C10 のスコアには現れないのに、実際には動かないコードが生成されることがあります。/ai-dev-os-check の自動修正は、そうした「スコアでは見えない実行時バグ」を拾ってくれます。実際に検出・修正された例を3つ紹介します。

例1: JWT_SECRET のハードコードフォールバック

ガイドライン導入後(After 条件)の5試行すべてで、以下のようなコードが生成されていました。

// After ガイドライン導入後でもこうなっていた
const secret = new TextEncoder().encode(
  process.env.JWT_SECRET ?? "fallback-secret-do-not-use-in-production"
);

// check後 → 環境変数未設定でエラーをスロー
if (!process.env.JWT_SECRET) {
  throw new Error("JWT_SECRET is not set");
}
const secret = new TextEncoder().encode(process.env.JWT_SECRET);

?? "fallback-secret..." というフォールバックは一見親切そうに見えますが、本番環境で JWT_SECRET が設定されていない場合でも静かに動き続けてしまいます。攻撃者に既知の文字列で署名されたトークンを受け入れてしまう、深刻なセキュリティ上の問題です。

check ツールは5試行中4試行でこれを検出・自動修正しました。残り1試行は未検出でした。ツール側にも検出ばらつきがある点は正直にお伝えしておきます。

例2: revalidatePath(キャッシュ無効化関数)の欠落

これはセキュリティ問題ではなく、UI の動作バグです。Server Actions(Next.js のサーバー側関数)でタスクを作成した後、revalidatePath を呼ばないと、新規タスクを追加してもリスト画面に反映されません。「保存できた!」と思ったのに一覧に出てこない、という見た目上は壊れているが原因が分かりにくいバグです。

// After: revalidatePath なし(タスク作成しても一覧が更新されない)
taskStore.push(task);
return { success: true, data: task };

// After+Check: 4/5 試行で自動追加
taskStore.push(task);
revalidatePath("/dashboard/tasks");  // ← check ツールが追加
return { success: true, data: task };

After 条件の5試行中4試行でこの欠落が発生していました。そしてこのバグ、C1〜C10 の採点では全試行 PASS だったのです。スコアだけ見ていたら見落としていたことになります。

例3: ミドルウェアのリダイレクト先が POST エンドポイント

middleware.ts で未認証ユーザーを /api/auth/login にリダイレクトするコードが生成されていました。このパスは POST 専用の API エンドポイントです。ブラウザからアクセスすると GET リクエストになるため、405 Method Not Allowed エラーになってしまいます。

C4(認証チェックあり)の採点では PASS していましたが、実際には「認証チェックはあるが、リダイレクト後に画面が表示されない」状態でした。check ツールがリダイレクト先を /api/auth/login から /login(UI ページ)に自動修正しました。

この3例から言えること: C1〜C10 のスコアが同じでも、実際のバグリスクは全然違います。「スコアが高い = 動く」ではなく、「スコアが高い + 実行時バグがない = 動く」という理解が必要です。check ツールの本当の価値は、スコアの加算よりもこの「動かないコードのバグを自動修正する」点にあると感じています。

解決しないこと(限界の正直な提示)

ここまで良いことばかり書いてきましたが、解決できないこともはっきりお伝えしておきます。

アーキテクチャが必要な項目は自動修正できない

プロンプトB(CRUD API)では、全15試行を通じて、C4(認証チェック)が 0% のままでした。

check ツールは「認証を追加してください」という指摘は出してくれます。しかし「どの Auth 基盤を使うか(NextAuth.js なのか、カスタム JWT なのか、など)」は人間が設計・決定する必要があります。ガイドラインは実装パターンを提示できますが、アーキテクチャ選定の代替にはなりません。

check ツールにも検出ばらつきがある

JWT_SECRET の例でお伝えしたとおり、5試行中1試行は未検出でした。ツールを過信せず、重要な箇所は人間によるレビューを組み合わせるのが現実的です。

型安全性のパラドックス

After 条件で C7(型安全性)スコアが Before より下がる逆転現象がありました。jose(JWT ライブラリ)や Zod(スキーマバリデーションライブラリ)のような高度な型定義を持つライブラリを使うほど、型を合わせるために明示的な as キャストが必要になる場面が増えます。「ライブラリが高度になるほど as が増える」という皮肉な状況です。

エラー分類(C5)は改善しにくい

C5(エラー分類)とは、エラーの種類を適切に分類(ValidationError / AuthError / ServerError など)して、それぞれに適切な HTTP ステータスとメッセージを返すチェック項目です。Before 20% 前後 → After も同程度でした。最小構成のサンプルコードでは例外が発生しにくいため、try-catch の適切な実装を AI に強制するのが難しいという構造的な問題があります。

まとめ

  • ガイドライン導入だけで +24点(56点 → 80点)。特に入力バリデーション(Zod)が 0% → 100% になったのが最大の貢献
  • check ツール追加でさらに +1点 + 実行時バグの自動修正。スコアへの貢献は小さいが、「動かないコードの修正」という実用上の価値が大きい
  • check ツールの真の価値はスコアより「動かないバグの自動修正」。JWT_SECRET フォールバック・revalidatePath 欠落・リダイレクト先誤りなど、採点では見えない問題を拾ってくれる
  • アーキテクチャ設計は人間が行う。ガイドラインと check ツールは「設計を実装に落とす精度」を上げるものであり、設計そのものを代替するものではない
  • ツールにも限界がある。検出ばらつきがあることを前提に、重要箇所は人間レビューと組み合わせる

皆さんのプロジェクトでは、AIが生成したコードのバリデーションはどうされていますか?コメント欄で教えてください!


GitHub リポジトリ: AI Dev OS(MIT License)

関連記事:

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?