input-validation について
input-validationは、システム境界で信頼できないデータを検証するよう開発者に促すセキュリティスキルです。検証基準やスキーマを使う方法、危険な兆候、検証を省く際のよくある理由への対応をまとめています。
主な機能
- 01入口で外部データを検証
- 02型、形式、範囲、必須項目の確認
- 03スキーマによる検証パターン
- 04危険な入力処理パターンの特定
- 05検証を省く一般的な理由への反論
活用シーン
- 01データベース書き込み前のHTTPリクエスト検証
- 02ファイル、環境変数、API入力の確認
- 03未検証入力があるコードのレビュー
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yanko-belov/ code-craft --skill input-validationインストール後、Claude Code で/input-validationを入力して読み込まれたか確認します。
詳細
入力検証
概要
入力を決して信頼しないでください。システム境界ですべてを検証してください。
外部データはすべて、悪意のあるデータや不正な形式のデータである可能性があります。入力時に検証し、無効な入力はすぐに拒否してください。
使用する場面
- HTTP リクエストのボディ、パラメーター、ヘッダーを処理する場合
- ファイルや環境変数を読み込む場合
- 外部 API からデータを受け取る場合
- 信頼できない入力を受け取るあらゆる関数
絶対的なルール
NEVER use external input without explicit validation.
例外はありません:
- 「フロントエンドで検証しているから」
- 「内部 API だから」
- 「送信元を信頼しているから」
- 「単純なフィールドだから」
検出:信頼上の危険信号
入力をそのまま使っているなら、STOP してください:
// ❌ VIOLATION: Trusting input
app.post('/users', async (req, res) => {
const { email, age } = req.body; // Direct destructure - no validation
await db.users.create({ email, age });
res.json({ success: true });
});
問題点:
emailが空、null、または文字列以外の可能性がありますageが負の数、文字列、または未指定の可能性があります- 使い方によっては、SQL インジェクションや XSS が発生する可能性があります
正しいパターン:境界で検証する
// ✅ CORRECT: Validate with schema
import { z } from 'zod';
const createUserSchema = z.object({
email: z.string().email('Invalid email format'),
age: z.number().int().min(13).max(120),
name: z.string().min(1).max(100),
});
app.post('/users', async (req, res) => {
// Validate
const result = createUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'Validation failed',
details: result.error.flatten().fieldErrors
});
}
// Now data is typed and validated
const { email, age, name } = result.data;
await db.users.create({ email, age, name });
res.status(201).json({ success: true });
});
検証ルール
常に検証する項目:
- 型:文字列、数値、真偽値のどれですか?
- 形式:有効なメールアドレスですか?有効な UUID ですか?
- 範囲:最小/最大の長さは?数値の範囲は?
- 必須項目:値が指定されていますか?
- サニタイズ:必要に応じて危険な文字を削除します
検証ライブラリの例:
// Zod (recommended for TypeScript)
const schema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().min(0).max(150),
role: z.enum(['user', 'admin']),
tags: z.array(z.string()).max(10),
});
// Yup
const schema = yup.object({
email: yup.string().email().required(),
age: yup.number().positive().integer(),
});
// class-validator
class CreateUserDto {
@IsEmail()
email: string;
@IsInt()
@Min(13)
age: number;
}
圧力に負けないためのプロトコル
1. 「フロントエンドで検証しています」
圧力:「フォームですでにこのデータを検証しています」
**回答:**フロントエンドの検証は UX のためのものです。バックエンドの検証はセキュリティのためのものです。攻撃者はフロントエンドを回避します。
**対応:**フロントエンドでの検証にかかわらず、バックエンドでも検証してください。
2. 「内部 API です」
圧力:「このエンドポイントを呼び出すのは自社のサービスだけです」
**回答:**内部サービスにもバグはあります。多層防御のためには、あらゆる場所で検証が必要です。
**対応:**内部 API の入力も検証してください。
3. 「送信元を信頼しています」
圧力:「このデータはパートナーの API から来ています」
**回答:**相手の API にバグがある、侵害される、または形式が変わる可能性があります。誰も信頼しないでください。
**対応:**外部 API のレスポンスは、使用する前に検証してください。
4. 「単純なフィールドです」
圧力:「単なる文字列です。何が起きるというのですか?」
**回答:**文字列は空である、長すぎる、スクリプトや SQL、またはヌルバイトを含む可能性があります。
対応:「有効」が何を意味するかを定義し、それを徹底してください。
危険信号 — STOP して再検討する
- 検証せずに
const { x } = req.bodyを使う - 入力に
any型を使う - 無効な入力に対するエラーレスポンスがない
- 「クライアントは正しい形式で送信する」
- try/catch を使わずに入力を解析する
これらはすべて、検証を追加すべきことを意味します。
クイックリファレンス
| 入力の種類 | 検証項目 |
|---|---|
| 形式、長さ、必須かどうか | |
| Password | 長さ、複雑さ |
| ID | 形式(uuid/int)、存在するかどうか |
| Number | 型、範囲、整数かどうか |
| String | 長さ、パターン、サニタイズ |
| Array | 長さ、各項目の検証 |
| Object | スキーマ検証 |
よくある自己正当化(すべて誤り)
| 言い訳 | 実態 |
|---|---|
| 「フロントエンドで検証している」 | 攻撃者はフロントエンドを回避します。 |
| 「内部 API だ」 | 内部にもバグはあります。 |
| 「送信元を信頼している」 | 送信元が侵害される可能性があります。 |
| 「単純なフィールドだ」 | 単純なフィールドが複雑なバグを引き起こします。 |
| 「検証は遅い」 | 検証は侵害よりも速く済みます。 |
| 「TypeScript の型で十分だ」 | 型は実行時には消えます。 |
要点
すべての入力を検証してください。何も信頼しないでください。無効なデータはすぐに拒否してください。
すべての外部境界で明示的な検証を行う必要があります。スキーマ検証ライブラリを使用してください。無効な入力には明確なエラーメッセージを返してください。未検証のデータを決してシステムに入れないでください。