ポストパッチ検証
報告されたバグとその影響を受ける周辺コードに対してセキュリティパッチを検証します。パッチ作成者に再現可能な失敗を提供し、まだテストが必要な部分を特定します。人間が作成したパッチとエージェントが生成したパッチに同じチェックを適用します。差分、作者、アップストリームの実装、元の概念実証だけでは正しさを確立できません。
使用するタイミング
- セキュリティ修正、修正コミット、パッチファイル、またはプルリクエストがすでに存在する場合。
- AIが生成したパッチを人間のレビューまたはマージの前に検証する必要がある場合。
- 修正が1つのエクスプロイト経路をカバーしているが、同じ根本原因のバリアントを見逃している可能性がある場合。
- セキュリティ修正が正当な動作を変更したり、新しい脆弱性を導入する可能性がある場合。
- パッチ作成者が別のリビジョンを作成する前に、具体的な失敗とカバレッジのギャップを必要としている場合。
使用してはいけないタイミング
- パッチがまだ存在しない場合。代わりに脆弱性の発見または修正の実装を先に行います。
- 監査結果をレポートに対して確認するタスクで、パッチの証拠を実行しない場合。
- タスクが単に監査結果を恒久的なプロジェクトテストに変換することだけの場合。
- 対象がリモートまたは本番環境の場合。このスキルはローカルコードとテストのみを実行します。
- ユーザーがリポジトリのコードまたはテストスイートの実行を許可していない場合。
クイックスタート
-
脆弱なベースとパッチ適用済みの入力を固定します。不変のコミットを推奨します。コミットされていない作業の場合は、最初にバイナリパッチファイルを作成します。ユーザーのワーキングツリーで検証しないでください。
-
固定された計画をスキャフォールドします:
uv run {baseDir}/scripts/post_patch_validation.py scaffold \ --repo . \ --base-ref <vulnerable-ref> \ --patched-ref <patched-ref> \ --finding-id <stable-id> \ --finding-summary "<根本原因と影響>" \ --evidence-level runtime \ --output post-patch-validation/plan.json--patched-refの代わりに--patch-file <path>を使用してパッチアーティファクトを指定します。最も正直な証拠レベルを選択してください:source(ソース/パッチ不変条件のみ)、build(ターゲットコードがコンパイルまたは分析されるが、報告された動作は実行されない)、runtime(チェックが報告された動作とその安全アサーションを実行する)。 -
調査結果、差分、呼び出し元、兄弟パス、クリーンアップ/エラーパス、既存のテストを検査します。生成された計画に
checksを追加します。構造スキーマを表示するにはprint-schemaを実行します:uv run {baseDir}/scripts/post_patch_validation.py print-schema -
コードを実行する前に、カバレッジ、コマンド制限、固定入力を含む完全な検証のために
validate-planを実行します:uv run {baseDir}/scripts/post_patch_validation.py validate-plan \ --plan post-patch-validation/plan.json -
証拠計画を実行します:
uv run {baseDir}/scripts/post_patch_validation.py run \ --plan post-patch-validation/plan.json \ --output post-patch-validation/results -
result.json、report.md、証拠レベル、および完全なassessmentを報告します。各調査結果を、そのチェックID、アサーション、保存されたログとともにパッチ作成者に返します。サポートされている調査結果と共存するギャップを含め、各検証ギャップを個別に特定します。作成者がパッチを修正した後、新しい入力を固定し、新しい検証実行を保存します。以前の証拠は保持します。提供されたチェックに合格しても、受け入れ前にはまだ人間によるレビューが必要です。
証拠コントラクト
ランナーは不完全な計画を拒否します。すべての種類のチェックを少なくとも1つ提供してください:
| 種類 | 必要な観測結果 |
|---|---|
control | 良性ハーネスがベースとパッチの両方で成功する |
exploit | 元の安全アサーションがベースで失敗し、パッチで成功する |
variant | 異なる根本原因バリアントがベースで失敗し、パッチで成功する |
behavior | 影響を受けない動作が、バイト同一の選択された出力で成功する |
regression | 対象となる非セキュリティリグレッションチェックが両方のリビジョンで成功する |
security | 隣接/新しい脆弱性のチェックがベースとパッチの両方で成功する |
suite | 既存のプロジェクトスイート、サニタイザー、または決定論的ファズキャンペーンがパッチで成功する |
コマンドはargv配列であり、シェル文字列ではありません。複雑なセットアップはチェックインまたは計画アーティファクトスクリプトに配置し、{plan_dir}で呼び出します。ランナーはロケール/タイムゾーン/ハッシュシード入力を固定し、チェックを辞書順IDで実行し、生のstdout/stderrを記録し、元のワークツリーを決して編集しません。各チェックのtimeout_secondsはデフォルトで300で、1から3600までの整数を受け入れます。タイムアウトを超えると検証ギャップが残ります。タイムアウトだけではリグレッションは確定しません。すべての計画にはソートされたsubmodules配列も含まれます(ない場合は[])。スキャフォールディングは、変更されたファイルのインベントリから影響を受けるGitlinkを推測します。ランナーは、ソースリポジトリの既存のGitモジュールオブジェクトからこれらの固定コミットを初期化し、.gitmodulesのネットワークURLからは決して初期化しません。検証の前に、ソースリポジトリでそれらを初期化またはフェッチしてください。
エクスプロイトとバリアントチェックは実行されたことを証明する必要があります
ゼロ以外の終了コードは、脆弱性が再現されたことを意味しません。インポートエラー、ビルド失敗、依存関係の欠落、安全アサーションの失敗はすべてゼロ以外で終了し、ランナーからは区別できません。すべてのexploitおよびvariantチェックは、両方のリビジョンで、アサーションを評価する直前にPPV_REACHEDを出力してフラッシュする必要があります:
"argv": ["python3", "-c", "import app; value = app.render('<'); print('PPV_REACHED', flush=True); assert value == '<"]
このトークンは環境変数PPV_REACHED_MARKERとしても利用できます。これは標準出力に、独自の行として出力されなければなりません。stderrはスキャンされません。なぜなら、PythonのSyntaxErrorトレースバックは問題のソースをエコーし、何も実行しなかったハーネスに対してチェックを満たしてしまうからです。これがない実行はmarker_missingギャップを残します。他のチェックからの独立した調査結果は結果に残ります。明示的にフラッシュしてください: ペイロードがセグメンテーションフォールトを起こしたり_exitを呼び出したりするハーネスは、バッファリングされた出力を失い、自身の証拠を無効にします。
これらのチェックはサイドブラインドでも実行されます。{side}はそれらに対して展開されず、PPV_SIDEは環境に存在せず、チェックアウトディレクトリはランダムに名前が付けられ、計画バリデーターはargvまたはenvのいずれかに{side}またはPPV_SIDEを含むexploitまたはvariantチェックを拒否します。自分がどのリビジョンにいるかを見ることができるアサーションは、コードに対してではなくそれに対してアサーションを行うことができ、これは再現とその後の修正を偽装する最も安価な方法です。
環境
チェックは固定された最小限の環境で実行されます: PATH、HOME、および少数の一時/ユーザーキーに加えて、LANG/LC_ALL=C、TZ=UTC、PYTHONHASHSEED=0、NO_COLOR、TERM=dumb。呼び出し側の環境の残りはすべて削除されます。さらに必要とするツールチェーンは明示的に指定します:
uv run {baseDir}/scripts/post_patch_validation.py run \
--plan post-patch-validation/plan.json \
--output post-patch-validation/results \
--allow-env JAVA_HOME --allow-env CARGO_HOME
転送された名前と値はresult.jsonに記録されます。要求された変数が設定されていない場合は、空の文字列ではなくエラーになります。2つのクラスは完全に拒否されます: 資格情報として読み取れる名前(*SECRET*、*TOKEN*、*API_KEY*など)。値が結果に書き込まれるためです。そして、実行内容を変更する名前(LD_PRELOAD、BASH_ENV、NODE_OPTIONS、GIT_SSH_COMMANDなど)。これらの変数は実行されるコードを変更できるためです。ランナーの固定変数とすべてのPPV_*名も予約されており、転送できません。
argvおよびチェックごとのenv値で展開されるプレースホルダー: {checkout}(テスト対象のリビジョン)、{plan_dir}(その1回の呼び出し用の計画アーティファクトの分離されたコピー)、{scratch}(その1回のチェック呼び出し用の新しい不透明なディレクトリ)、および{side}(baseまたはpatched、exploit/variantチェックでは使用不可)。同じ値がPPV_CHECKOUT、PPV_PLAN_DIR、PPV_SCRATCH、PPV_SIDE、PPV_CASE_IDとして提供されます。{scratch}の下にのみ書き込みます。証拠ディレクトリのパスはチェックに渡されません。ベースとパッチの呼び出しは、ランナー管理のスクラッチ、計画、またはワークツリールートを共有しません。各呼び出しが終了すると、そのスクラッチツリーは決定論的なresults/scratch/<check-id-and-side>パスの下にアーカイブされ、プライベートな計画コピーは破棄され、ファイルに解決される読み取り可能な各argv要素はargv_filesでハッシュ化されます。分離された計画またはチェックアウトルート内のファイルは、さらにresults/helpers/<sha256>の下に最大16 MiBまで保持されます。レコードは、他のファイルがアーカイブされなかった理由を説明します。plan.jsonには専用のディレクトリを使用してください。その兄弟ファイルとディレクトリは、各呼び出しの{plan_dir}にコピーされます。ヘルパーコードはそのディレクトリのchecks/ディレクトリの下、またはターゲットリポジトリにチェックインして、そのバイトがレビュー可能になるようにしてください。コミットピン、現在の出力ディレクトリ、および検出された以前の結果ツリーを含むマシン計画は除外されます。シンボリックリンクは拒否されます。クリーンなスナップショットはランナーメモリにのみ残り、エクスプロイト/バリアントのサイドはランダムな順序で実行されますが、証拠ファイル名は決定論的です。Stdout/stderrは匿名またはランダムに名前が付けられたキャプチャ記述子を使用し、子プロセスが終了した後にのみ名前付き証拠ファイルにコピーされるため、fd検査でサイドラベルが漏洩することはありません。
この隔離はホストサンドボックスではありません。チェックは呼び出し側の権限で実行され、悪意のあるヘルパーは任意の外部状態を使用したり、ソースまたはGitメタデータからリビジョンを意図的に推測したりする可能性があります。コンテンツアドレス指定されたヘルパーアーティファクトを検査し、チェックコード自体が信頼できない場合は、OS/コンテナサンドボックスを使用してください。
アクティブな検証ワークツリーは、カーネルファイルロックによってバックアップされたランダムな所有者トークンでGitロックされるため、別の同時バリデーターがそれらを削除できず、PIDの再利用が所有者を偽装することはできません。ランナーが強制終了された場合、次の実行は古いバリデーター所有の登録をロック解除します。手動で回復するには、git worktree listを検査し、次にgit worktree unlock <path>とgit worktree remove --force <path>を使用します(パスがなくなった後はgit worktree prune)。
カバレッジの設計、バリアントの選択、または調査結果と検証ギャップの解釈には、evidence-model.mdを読んでください。日常的なCLI実行のためにそれを読む必要はありません。
カバレッジルール
- 元のペイロードの表面的な変形ではなく、根本原因からバリアントを導出します。
- 修正によって影響を受ける、兄弟呼び出しサイト、代替コールバック/出力、エラーパス、後処理、所有権、シリアル化、および境界値を列挙します。
- 各エクスプロイトまたはバリアントテストに安全な動作をアサートさせます。脆弱なベースで失敗する必要があります。両方のリビジョンで合格するテストは、修正について何も証明しません。ベース側のstderrを読み取り、失敗があなたが書いたアサーションであり、そこに到達しなかったハーネスではないことを確認してください。
- エクスプロイトとバリアントのアサーションをセキュリティ不変条件に限定します。生存性、正確なエラータイプ/メッセージ、タイミング、および互換性は、別途
behaviorまたはregressionチェックとしてテストしてください。そうしないと、無関係な契約変更が脆弱性が残っていることの証明になりすます可能性があります。 controlハーネスは良性に保ち、変更されたコンポーネントを実行させます。これにより、ハーネスが両方のリビジョンで機能することが確立されます。コントロールの失敗はギャップを残し、他の失敗をパッチに帰属させることを妨げます。生の観測結果はレビューのために引き続き利用できます。behaviorは、変更されるべきではない動作にのみ使用します。正確な出力比較は意図的なものです。不安定な値は、説明文で正規化して取り除くのではなく、決定論的なテストハーネスの背後に移動してください。securityチェックは、パッチ適用後の失敗を新たに導入されたものとして扱う前に、脆弱なベースで合格するようにしてください。ベースラインの失敗は帰属を未解決のままにします。- 検証中にパッチを編集しないでください。失敗をパッチ作成者に返し、新しく固定された新しい実行を開始してください。
結果の読み方
result.jsonスキーマ2.0には、status、findings、gaps、およびhuman_review_requiredを持つassessmentが含まれています。ステータスは、すべての必須チェックが使用可能な証拠を生成した場合(一部のチェックで失敗が見つかった場合でも)completeになります。ギャップが残っている場合、ステータスはincompleteです。調査結果は、チェックID、チェック種類、および失敗した期待値を示します。ギャップは、チェックIDと欠落している証拠を示し、実行レベルの問題(クリーンアップ失敗など)の場合はチェックIDがnullになります。
スイートチェックは最初にパッチ適用されたリビジョンで実行されます。完了した失敗は、同じチェックをベースラインでトリガーします。ベースラインが合格した場合、パッチ後の失敗を報告します。両方が失敗した場合、両方のログを保持し、帰属が未解決であることを報告します。終了コードが同じことが同じ失敗を意味するとは想定しないでください。
ランナーは、調査結果のない完全なチェックの場合は0で終了し、調査結果のある完全なチェックの場合は1で終了し、不完全な検証の場合は10で終了し、無効な入力の場合は64で終了します。終了コード10の場合でもアーティファクトを読んでください。ギャップとともにサポートされている調査結果が含まれている可能性があります。ソースまたはビルドの証拠はランタイム動作を確立できません。合格結果と一緒に証拠レベルを記載してください。
Claude Dynamic Workflow
Claude Codeはバンドルされたワークフローを/post-patch-validation:validate-patchとして公開します。Workflowツールを通じて構造化入力を渡すには、先頭のスラッシュなしの名前を使用してください:
Workflow({
name: 'post-patch-validation:validate-patch',
args: {
finding: '<調査結果のテキストまたはローカルパス>',
baseRef: '<脆弱なリファレンス>',
patchRef: '<パッチ適用されたリファレンス>',
workdir: 'post-patch-validation',
},
})
必要に応じてpatchRefの代わりにpatchFileを使用します。ワークフローは固定されたカバレッジレンズを使用してチェックを提案し、固定されたエグゼキュータを使用してこのスキルを実行します。エージェントはテストアーティファクトを作成する場合があります。Pythonランナーは調査結果とギャップを記録し、レビュアーはカバレッジまたは証拠の問題を個別に報告します。NEEDS_REPAIRは、他のチェックがギャップを残した場合でも、サポートされている失敗を返します。BLOCKEDは、サポートされている失敗なしでは証拠が不完全であることを意味します。合格したチェックは、両方の証拠レビューが承認した後にのみREADY_FOR_HUMAN_REVIEWに進みます。そうでない場合はREVIEW_REQUIREDになります。ワークフローは起動後に質問をすることができないため、すべての入力を事前に渡してください。
拒否すべき正当化
| 正当化 | 必要な再対応 |
|---|---|
| パッチは単純です | 簡潔さは正しさではありません。検証をスキップしないでください。 |
| テストがパスします | 既存のテストは未知のバリアントをカバーしていません。 |
| コードレビューが承認しました | レビューだけでは実行時の動作を証明できません。 |
| ツールが生成しました | すべての出力は同じ検証基準を満たさなければなりません。 |
| 以前は機能していました | リビジョンが異なれば動作も異なります。 |
トラブルシューティング
- 計画検証エラー:
validate-planの出力を確認し、すべての必須フィールドが存在することを確認してください。 - 証拠マーカーが見つからない: エクスプロイト/バリアントのハーネスが
PPV_REACHEDをstdoutにフラッシュしていることを確認してください。 - タイムアウト: タイムアウト値を増やすか、チェックの複雑さを軽減してください。
- 依存関係の欠落: チェックが依存関係を明示的にインストールしているか、または環境に存在することを確認してください。
- Gitワークツリーエラー:
git worktree listを実行し、古いロックをクリーンアップしてください。 - 環境変数エラー:
--allow-envで転送するすべての変数が設定されていることを確認してください。