defense-in-depth-validation について
Defense-in-Depth Validationは、無効なデータによるバグを減らすための開発者向け実践ガイドです。入口、ビジネスロジック、環境ガード、デバッグ記録の各層に検証を配置します。
主な機能
- 01入口での入力検証
- 02ビジネスロジックの検証
- 03環境依存操作のガード
- 04デバッグ情報の記録
活用シーン
- 01無効なパスによる意図しないGit操作の防止
- 02複数のデータ経路における検証漏れの確認
- 03マルチテナント要求への段階的な認可チェックの追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill defense-in-depth-validationインストール後、Claude Code で/defense-in-depth-validationを入力して読み込まれたか確認します。
詳細
多層防御による検証
概要
無効なデータが原因のバグを修正するとき、1か所で検証を追加すれば十分だと感じることがあります。しかし、その単一のチェックは、異なるコードパスやリファクタリング、モックによって回避される可能性があります。
基本原則: データが通過するすべての層で検証します。バグが構造上起こり得ないようにします。
複数の層が必要な理由
単一の検証:「バグを修正しました」 複数の層:「バグが起こり得ないようにしました」
層ごとに異なるケースを検出します。
- エントリーポイントでの検証は、ほとんどのバグを検出します
- ビジネスロジックでの検証は、エッジケースを検出します
- 環境ガードは、特定のコンテキストでの危険を防ぎます
- デバッグログは、ほかの層で検出できなかった場合に役立ちます
4つの層
レイヤー 1:エントリーポイントでの検証
目的: API の境界で明らかに無効な入力を拒否します
function createProject(name: string, workingDirectory: string) {
if (!workingDirectory || workingDirectory.trim() === '') {
throw new Error('workingDirectory cannot be empty');
}
if (!existsSync(workingDirectory)) {
throw new Error(`workingDirectory does not exist: ${workingDirectory}`);
}
if (!statSync(workingDirectory).isDirectory()) {
throw new Error(`workingDirectory is not a directory: ${workingDirectory}`);
}
// ... proceed
}
レイヤー 2:ビジネスロジックでの検証
目的: データがこの操作に適していることを確認します
function initializeWorkspace(projectDir: string, sessionId: string) {
if (!projectDir) {
throw new Error('projectDir required for workspace initialization');
}
// ... proceed
}
レイヤー 3:環境ガード
目的: 特定のコンテキストで危険な操作が行われないようにします
async function gitInit(directory: string) {
// In tests, refuse git init outside temp directories
if (process.env.NODE_ENV === 'test') {
const normalized = normalize(resolve(directory));
const tmpDir = normalize(resolve(tmpdir()));
if (!normalized.startsWith(tmpDir)) {
throw new Error(
`Refusing git init outside temp dir during tests: ${directory}`
);
}
}
// ... proceed
}
レイヤー 4:デバッグ用の計測
目的: 調査に必要なコンテキストを記録します
async function gitInit(directory: string) {
const stack = new Error().stack;
logger.debug('About to git init', {
directory,
cwd: process.cwd(),
stack,
});
// ... proceed
}
パターンの適用
バグを見つけたときは、次の手順に従います。
- データフローをたどる - 不正な値はどこで発生し、どこで使われていますか?
- すべてのチェックポイントを洗い出す - データが通過するすべてのポイントを列挙します
- 各層に検証を追加する - エントリーポイント、ビジネスロジック、環境、デバッグ
- 各層をテストする - レイヤー 1 を回避し、レイヤー 2 が検出することを確認します
セッションでの例
バグ:空の projectDir によって、ソースコード内で git init が実行されました
データフロー:
- テストのセットアップ → 空文字列
Project.create(name, '')WorkspaceManager.createWorkspace('')process.cwd()内でgit initが実行される
追加した4つの層:
- レイヤー 1:
Project.create()で空でないこと、存在すること、書き込み可能であることを検証 - レイヤー 2:
WorkspaceManagerでprojectDirが空でないことを検証 - レイヤー 3:テスト中、
WorktreeManagerが tmpdir の外での git init を拒否 - レイヤー 4:git init の前にスタックトレースを記録
結果: 1847 件のテストがすべて成功し、バグを再現できなくなりました
重要なポイント
4つの層すべてが必要でした。テスト中、各層はほかの層が見逃したバグを検出しました。
- 異なるコードパスがエントリーポイントでの検証を回避しました
- モックがビジネスロジックのチェックを回避しました
- 異なるプラットフォームでのエッジケースには環境ガードが必要でした
- デバッグログによって構造上の誤用が特定されました
検証ポイントを1つ設けただけで終わりにしないでください。 すべての層にチェックを追加します。
セキュリティの多層防御
同じ多層アプローチは、セキュリティ制御の標準的なパターンです。信頼は単一のポイントで与えないため、どの層でバグや回避が発生しても、それだけでシステムが侵害されることはありません。各リクエストは、エッジ、ハンドラー、データ層でそれぞれ独立してチェックされます。
- レイヤー 1(エッジ):エッジ(Cloudflare や AWS WAF などの CDN/WAF)でレート制限とフィルタリングを行い、不正なトラフィックがオリジンに到達しないようにします。
- レイヤー 2(ハンドラー):セッションに基づいてサーバー側で認可を再導出します — 本文の
userIdのように、クライアントが指定した申告を決して信頼しないでください。 - レイヤー 3(データ):データベースクエリで所有権の不変条件を強制し、ハンドラーにバグがあってもテナントの境界を越えられないようにします。
// Layer 2 — re-derive authz server-side, never trust client claims
app.put('/docs/:id', async (req, res) => {
const user = await verifySession(req.cookies.session); // throws if invalid
// Layer 3 — DB clause re-checks ownership; a handler bug still can't cross tenants
const updated = await db.query(
'UPDATE docs SET title = $1 WHERE id = $2 AND owner_id = $3 RETURNING *',
[req.body.title, req.params.id, user.id]
);
if (!updated.rowCount) return res.status(403).send('Not allowed');
res.json(updated.rows[0]);
});
多層防御とは、どの単一の層で障害が発生しても、それだけでシステムが侵害されることはないということです。