アンチパターン⑩「入力バリデーション欠如」
$2M不正決済事件
2025年3月、衝撃的な事件が発生しました。
“In March 2025, a vibe-coded payment gateway approved $2M in fraudulent transactions due to inadequate input validation.”
vibe codingで作られた決済ゲートウェイが、入力バリデーション不備により 200万ドル(約3億円) の不正決済を承認してしまいました。
入力バリデーションとは:AI生成コードの脆弱性対策
入力バリデーションとは、ユーザーからの入力データが期待される形式・範囲であることを確認するプロセスです。
なぜ必要か:
- 不正な入力の防止: 想定外のデータを弾く
- セキュリティ: インジェクション攻撃の防止
- データ整合性: データベースの一貫性維持
- ユーザー体験: エラーメッセージによるガイド
【要注意】典型的な危険コードパターン
AIが生成しがちなバリデーション不足のコードを見てみましょう。
危険なコード:
// ❌ バリデーションなし
app.post('/payment', async (req, res) => {
const { amount, cardNumber, cvv } = req.body;
// 直接処理(何もチェックしていない)
await processPayment(cardNumber, amount);
res.json({ success: true });
});
攻撃の例:
// 負の金額で返金を騙し取る
{ "amount": -10000, "cardNumber": "1234567890123456" }
// 異常に大きな金額
{ "amount": 999999999, "cardNumber": "..." }
// SQLインジェクション
{ "amount": "100; DROP TABLE users;--" }
なぜAIはバリデーションを省略するのか
AIがバリデーションを省略する理由があります。
理由1: 「動く」ことが優先
- 最短で動くコードを生成
- バリデーションは「追加作業」
理由2: 明示的に依頼されていない
- 「決済機能を作って」だけでは不十分
- セキュリティ要件を指示しないと考慮しない
理由3: トレーニングデータの問題
- チュートリアルはバリデーションを省略しがち
- 実務レベルのセキュリティは含まれていない
実践的な解決策:厳密なバリデーション
正しいバリデーションの実装方法を解説します。
実践的な解決策1: zodを使ったスキーマバリデーション
import { z } from 'zod';
const paymentSchema = z.object({
amount: z.number()
.positive('金額は正の数である必要があります')
.max(100000, '1回の決済上限は10万円です'),
cardNumber: z.string()
.regex(/^\d{16}$/, '16桁の数字で入力してください'),
cvv: z.string()
.regex(/^\d{3,4}$/, 'CVVは3〜4桁の数字です'),
expiryMonth: z.number()
.min(1).max(12),
expiryYear: z.number()
.min(2025).max(2035),
});
app.post('/payment', async (req, res) => {
const result = paymentSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.issues
});
}
await processPayment(result.data);
res.json({ success: true });
});
実践的な解決策2: サニタイズ
import sanitizeHtml from 'sanitize-html';
import validator from 'validator';
// HTMLのサニタイズ
const cleanHtml = sanitizeHtml(userInput, {
allowedTags: ['b', 'i', 'em', 'strong'],
allowedAttributes: {}
});
// SQLインジェクション対策(プリペアドステートメント)
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId]);
// XSS対策(エスケープ)
const escaped = validator.escape(userInput);
実践的な解決策3: 型チェック
interface PaymentRequest {
amount: number;
cardNumber: string;
cvv: string;
expiryMonth: number;
expiryYear: number;
}
function validatePayment(data: unknown): PaymentRequest {
if (typeof data !== 'object' || data === null) {
throw new Error('Invalid request body');
}
const { amount, cardNumber, cvv, expiryMonth, expiryYear } = data as any;
if (typeof amount !== 'number' || amount <= 0) {
throw new Error('Invalid amount');
}
// ... 他のフィールドも検証
return { amount, cardNumber, cvv, expiryMonth, expiryYear };
}
バリデーションの原則
効果的なバリデーションの原則があります。
原則1: ホワイトリスト方式
- 許可する値を明示
- 禁止する値を列挙するのではなく
原則2: サーバーサイドで必ず検証
- クライアントサイドのバリデーションは補助
- サーバーサイドが最終防衛線
原則3: 早期に失敗
- 問題があれば即座にエラー
- 処理を続行しない
原則4: エラーメッセージは慎重に
- ユーザーには必要最小限
- 攻撃者にヒントを与えない
プロンプトによるセキュリティ対策
AIに決済機能を依頼する際のプロンプト例です。
良いプロンプト:
決済機能を実装して:
## バリデーション要件
- zodスキーマでリクエストを検証
- 金額: 正の数、上限10万円
- カード番号: 16桁の数字
- CVV: 3〜4桁の数字
- 有効期限: 有効な日付
## 必須のセキュリティ要件
- サーバーサイドでバリデーション
- 不正な入力は400エラー
- エラーメッセージは最小限
- レート制限を実装
実装前チェックリスト
入力バリデーションのチェックリストです。
実装時:
- すべての入力フィールドにバリデーションがあるか
- 型チェックを行っているか
- 範囲チェックを行っているか
- 形式チェック(正規表現等)を行っているか
レビュー時:
- サーバーサイドでバリデーションしているか
- 負の値、ゼロ、異常に大きな値を弾いているか
- SQLインジェクションを防いでいるか
- エラーメッセージが詳細すぎないか
この事例から学ぶべき教訓と実践ポイント
「入力バリデーション欠如」から学ぶべきことは以下の通りです。
-
$2Mの不正決済が発生
- 入力バリデーション不備が原因
-
AIはバリデーションを省略しがち
- 明示的に依頼が必要
-
zodなどのスキーマバリデーション
- 型安全で堅牢
-
サーバーサイドが最終防衛線
- クライアントは信用しない
-
ホワイトリスト方式
- 許可する値を明示
まとめ:重要ポイントの振り返り
- $2Mの不正決済 が入力バリデーション不備で発生
- AIは バリデーションを省略 しがち
- 解決策:zodスキーマ、サニタイズ、型チェック
- サーバーサイド が最終防衛線
- ホワイトリスト方式 で許可する値を明示
- プロンプトで バリデーション要件を明示
- 教訓:ユーザー入力は絶対に信用するな