security-defensive-programming について
外部入力、エラー、アクセス制御、機密データを扱う際の防御的プログラミングを支援するコーディングスキルです。開発者向けに具体的な対策と変更後のセキュリティチェック項目を示します。
主な機能
- 01システム境界での入力検証
- 02安全な API によるインジェクション防止
- 03内部情報を漏らさないエラー処理
- 04認証と認可の適用
- 05シークレットと機密データの保護
- 06セキュリティチェックリストによる確認
活用シーン
- 01信頼できない入力を受け取る API の実装
- 02保護対象の操作への認可追加
- 03データベースやネットワーク障害への対応
- 04コード変更に潜む一般的な安全上の問題の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ xip-online-applications/ skills --skill security-defensive-programmingインストール後、Claude Code で/security-defensive-programmingを入力して読み込まれたか確認します。
詳細
security-defensive-programming
誤用や無効な入力、予期しない状況を想定してコードを書きます。状態を気付かないうちに破損させたり、脆弱性を露呈させたりするのではなく、安全に失敗させます。
使用する場面
次のような場合にこのスキルを使用します。
- 外部入力(HTTP リクエスト、CLI 引数、ファイルの内容、環境変数、データベースの結果、メッセージキュー)を受け取る関数を新規作成または変更する場合
- API、コントローラー、サービス、データ処理ロジックを作成する場合
- 認証、認可、セッション管理を扱う場合
- ファイルシステム、データベース、ネットワーク呼び出しを扱う場合
- 動的な値を含むクエリ、コマンド、出力を組み立てる場合
- 信頼できない、または半分しか信頼できないソースのデータを処理する機能の実装をユーザーから依頼された場合
中核となる概念
防御的プログラミングでは、すべての入力は安全性が確認されるまで悪意のあるものとして扱うこと、そしてどの操作も失敗する可能性があることを前提とします。目的は過剰に複雑な設計をすることではなく、適切な境界で一貫性のある最小限の安全対策を適用し、脆弱性のクラス全体を防ぐことです。
主な原則は次のとおりです。
- 境界で検証し、その内側では信頼します。 エントリーポイント(コントローラー、ハンドラー、公開 API メソッド)で入力を一度検証します。その境界チェックを通過した後は、内部関数で検証済みのデータとして扱えます。
- 速やかに、明示的に失敗させます。 破損したデータがシステムの奥まで伝播するのを防ぐため、無効な状態は明確なエラーを返してすぐに拒否します。
- 最小権限を適用します。 操作に必要な最小限のアクセス権、スコープ、機能のみを付与します。
- 多層防御を行います。 単一のレイヤーに依存しないでください。入力検証、パラメーター化クエリ、出力エンコーディング、アクセスチェックを組み合わせます。
手順
1. 入力検証
外部入力を受け取るすべての関数で、次の手順を実行します。
- 期待する形式を定義します。 処理する前に、型、形式、長さ、範囲、許可する文字を指定します。
- 一致しない入力はすべて拒否します。 無効なものをブラックリスト化するのではなく、有効なものを定義する許可リスト方式を使用します。
- 早い段階で検証します。 データがビジネスロジックに到達する前に、システム境界(コントローラー、API ハンドラー、CLI パーサー)で検証します。
- 必要に応じてサニタイズします。 特殊文字に意味があるコンテキスト(HTML、SQL、シェル、正規表現)に入力を渡す必要がある場合は、コンテキストに適したエンコーディングまたはエスケープを適用します。より望ましいのは、パラメーター化 API を使用することです。
- 明示的にオプションでない限り、値がない状態は無効として扱います。 必須フィールドが存在しない場合は拒否します。安全でない値を暗黙にデフォルトとして設定してはいけません。
判断基準:
- 言語やフレームワークにスキーマ/検証ライブラリ(例:Zod、Joi、class-validator、Pydantic、JSON Schema)がある場合 → それを使用します。
- 利用できるライブラリがない場合 → 関数の入口に明示的なガード節を記述します。
2. 出力エンコーディングとインジェクションの防止
ユーザー提供データを文字列連結して、実行可能な文字列(SQL、HTML、シェルコマンド、LDAP クエリ、ログエントリー)を組み立ててはいけません。
- SQL → 必ずパラメーター化クエリまたはプリペアドステートメントを使用します。値を直接埋め込んではいけません。
- HTML → 自動エスケープが有効なフレームワークのテンプレートエンジンを使用します。生の HTML を出力する必要がある場合は、十分にテストされたサニタイザーライブラリを使用します。
- シェルコマンド → ユーザー入力を使ってシェルプロセスを起動するのは避けます。避けられない場合は、配列ベースの API(例:
execではなくexecFile、subprocess.run(shell=True)ではなくsubprocess.run([...]))を使用します。 - ログ → ログインジェクションや偽造を防ぐため、ユーザー入力をログに書き込む前にサニタイズまたはエンコードします。
- URL とリダイレクト → リダイレクト先をドメインまたはパスの許可リストに照らして検証します。検証されていないユーザー指定の URL にリダイレクトしてはいけません。
3. エラー処理と障害時の安全性
- 具体的なエラーを捕捉してください。 少なくともログを記録するか、再スローしない限り、
catchやexceptのみのブロックは決して記述しないでください。想定される失敗モードだけを捕捉してください。 - 内部情報を公開しないでください。 ユーザーに返すエラーメッセージに、スタックトレース、ファイルパス、データベーススキーマ、内部状態を含めてはいけません。詳細はサーバー側でログに記録し、クライアント側には汎用的なメッセージを返してください。
- アクセスをデフォルトで拒否してください。 セキュリティチェックでアクセスを許可すべきか判断できない場合(例:認証サービスに接続できない場合)は、デフォルトでアクセスを拒否してください。
- リソースをクリーンアップしてください。 try-finally、
using/withブロック、または RAII パターンを使って、すべてのコードパスでファイルハンドル、接続、ロックが解放されるようにしてください。 - エラーを握りつぶさないでください。 例外を捕捉した場合は、適切に処理するか、再スローするか、ログに記録してください。空の catch ブロックは決して許容されません。
4. 認証と認可
- 暗号化や認証を独自に実装しないでください。 十分に実績のあるライブラリやプラットフォーム機能を使用してください。
- 保護対象の操作ごとに認可を確認してください。 UI レベルで非表示にするだけに頼らず、サーバー側でアクセスルールを適用してください。
- シークレットの比較には定時間比較を使用してください。 トークン、ハッシュ、API キーの比較には、タイミングセーフな比較関数を使用してください。
- セッションとトークンを適切に無効化してください。 ログアウト時や権限変更時に、古いトークンを再利用できないようにしてください。
5. シークレットと機密データの取り扱い
- シークレットをハードコードしないでください。 API キー、パスワード、トークン、証明書は、環境変数、シークレットマネージャー、または暗号化された設定から取得してください。ソースコードに記述してはいけません。
- シークレットをログに記録しないでください。 リクエスト/レスポンスオブジェクトをログに記録する前に、機密フィールド(パスワード、トークン、クレジットカード番号、PII)を除去するか、マスキングしてください。
- メモリ上の機密データを最小限にしてください。 パスワードを必要以上に長く平文の変数に保存しないでください。言語で可能な場合は、使用後にバッファをクリアしてください。
- すべての外部通信で HTTPS/TLS を使用してください。 暗号化されていないチャネルで機密データを送信しないでください。
6. null/undefined の安全性
- プロパティへのアクセスやメソッドの呼び出し前に、null/undefined でないことを確認してください — 言語の型システムによって null でないことが保証される場合を除きます。
- 型システムを使ってオプション性を表現してください。 実行時チェックだけに頼るのではなく、
Optional<T>、nullable 型、またはユニオン型を優先してください。 - 呼び出し元が値の不在を処理できない場合は、null を返すのではなく、意味のあるデフォルト値またはエラー値を返してください。
7. 並行処理と競合状態
- 共有される可変状態を特定してください。 2 つのコードパスが同じリソースを並行して読み書きできる場合は、ミューテックス、トランザクション、アトミック操作で保護してください。
- チェック時と使用時の間に発生する競合(TOCTOU)を避けてください。 権限チェックと、そのチェックで保護する操作を分離せず、可能な場合はアトミックに実行してください。
- 複数回トリガーされる可能性のある操作(再試行、メッセージの再配信)には、冪等性パターンを使用してください。
8. 依存関係とサプライチェーンへの配慮
- 依存関係のバージョンを固定してください。 ロックファイルを使用し、本番環境の依存関係には正確なバージョンを指定してください。
- 依存関係を追加する前に、既知の脆弱性がないか確認してください(
npm audit、pip-audit、trivy、Dependabot などのツールを使用してください)。 - 依存関係の範囲を最小限にしてください。 十分に保守され、広く利用されているライブラリを優先してください。単純な機能のために依存関係を追加しないでください。
9. 自己検証チェックリスト
コードの作成または変更後に、次の点を確認してください。
- すべての外部入力が使用前に検証されている。
- サニタイズされていないユーザーデータを使って、SQL、HTML、シェル、ログのコンテキスト向けの動的文字列を組み立てていない。
- エラーが明示的に処理され、内部の詳細が漏えいしない。
- シークレットがハードコードされたり、ログに記録されたりしていない。
- 保護対象の操作について、サーバー側で認可が適用されている。
- nullable 値へのアクセス前にチェックされている。
- すべてのコードパスでリソース(接続、ハンドル)がクリーンアップされている。
- 明確な必要性と脆弱性チェックなしに、新たな依存関係を導入していない。
避けるべきアンチパターン
- 「フロントエンドを信頼する。」 クライアント側の検証だけで十分だと決して思わないでください。必ずサーバー側でも再検証してください。
- 「これは社内だけで使うものです。」 社内サービスも侵害されます。ネットワーク上の位置にかかわらず、同じ防御原則を適用してください。
- 「セキュリティ対策は後で追加します。」 後付けのセキュリティ対策は脆弱です。最初から組み込んでください。
- 「問題のあるものをブラックリストに登録する。」 ブラックリストは常に不完全です。許可するものを定義するには、許可リストを使用してください。
- 「すべての例外を捕捉して、すべて無視する。」 例外を握りつぶすと、バグやセキュリティ上の失敗が隠れてしまいます。すべてのエラーを処理するか、伝播させてください。
- 「自分の環境では動きます。」 環境の違いによって、前提の誤りが明らかになります。設定を検証し、必須の値が不足している場合は速やかに失敗させてください。
コメント
Markdown 対応