サプライチェーン監査の設計
コミット済みのサプライチェーン監査テストの背後にある検証の原則
サプライチェーン監査は、コミット済みファイルだけからリポジトリの依存関係制御を証明します。 騙される可能性のある監査(あるいは何もチェックせずにパスしてしまう監査)は、監査がないことよりも悪質です。 なぜなら、グリーンが未検証の状態を保証してしまうからです。 以下の原則は、制御に違反している状態で監査がパスしてしまうことを防ぎ、監査を実行する価値のあるものに保ちます。
どのような制御が存在しなぜ必要なのかについては、サプライチェーンセキュリティを参照してください。 監査が PR で失敗した場合の対処方法については、依存関係ドキュメントの監査セクションを参照してください。
原則
- コミット済みファイルだけから証明する。
監査はロックファイル、マニフェスト、
.npmrc、netlify.tomlを読み取ります(ネットワークやインストール済みツリーは読み取りません)。 そのため高速でオフラインであり、検証対象の状態によって左右されることがありません。 - パターンの拒否リストではなく、全体の形状を許可リストに登録する。 設定フォーマットに対する拒否リスト正規表現は、リストにない有効な記法を見逃します(引用付き、ドット付き、インラインテーブルの TOML キーはすべて env キー拒否リストをバイパスしました)。 レビュー済みの形状全体(正確なキーセット、正確な値)を固定する方がより強力で、通常はより簡潔です。
- 行スキャンではなく、パースする。 フォーマットのパーサーがそのセマンティクスを定義します。 行の正規表現はそれを暗黙的に誤ってモデル化します。 認識できないコンテキストテーブルも Netlify にとっては意味を持ちます。
- 正確な固定。接頭辞やフラグのマッチングを行わない。
接頭辞マッチングは、監査が名前で信頼するスクリプトに付加された
&& npm install ...ライダーを受け入れてしまいます。 - 不在時にはクローズドで失敗する。 すべてのカウントチェックにはフロアアサーションが含まれており、空の入力は真空的にパスすることができません。 期待されるフィールドがないエントリは、スキップされるのではなく失敗します。
- npm が信頼するアイデンティティにバインドする。
npm はパッケージのアイデンティティをロックキーや
versionフィールドではなく、resolvedレジストリ URL から導出します。 そのため監査は 3 つすべてを結びつけます。 npm が信用しないフィールドだけをチェックすると、npm が作用するミスマッチを見逃してしまいます。 - 1つの不変条件に1つのホーム。 2 つのファイルでアサートされたリストはドリフトします。 監査は共有値をその所有モジュールからインポートし、そのモジュールのユニットテストが内容を固定します。
- レッドファースト。 新しいチェックは、意図的に壊された入力がそれを失敗させた後にのみ信頼されます。 偽のグリーンはレッドよりも悪いのです。
- アサーションは期待される条件を命名し、よくある障害については修正方法も命名します。
allowScriptsアサーションは依存関係のバンプが移動先とすべきバージョンを命名するため、失敗メッセージがそのまま修正手順になります。 - スコープ境界を明示する。 監査が意図的にカバーしない領域は監査と依存関係ドキュメントに明記されており、カバレッジの不在が検証済みカバレッジと誤認されることはありません。
- すべてのチェックはその存在価値を示す。 セキュリティは制御の設計とレビューから生まれるのであり、アサーションの蓄積からではありません。 各アサーションは変更のたびにコントリビューターとメンテナーに負担をかけます。 チェックがここに属するのは、レビューでは明らかに検出できないものを検出する場合に限ります。 たとえば、不透明で高い権威性を持つロックファイルや、目視では捉えられないほど難解なセマンティクスです。 制御がすでに実行時にクローズドで失敗する場合、監査はそれを再度検証しません。 チェックの維持コストがその検出価値を上回った場合、それを削減することは弱体化ではなく正しい判断です。
監査における原則
各原則について 1 つの実例を示します。 テストファイルがアサーションの正式な一覧です。
| 原則 | 監査における実例 |
|---|---|
| コミット済みファイルだけ | すべての入力はチェックアウトから読み取られ、スイートはネットワークなしで実行されます |
| 全体の形状を許可リストに登録 | netlify.toml: トップレベルテーブル、ビルドキー、env キーセットは deepEqual で比較されます |
| 行スキャンではなくパース | netlify.toml はアサーションの前に smol-toml でパースされます |
| 正確な固定 | インストールクロージャスクリプトは assert.equal で比較され、match は使われません |
| 不在時にはクローズドで失敗 | registryPackages > 0 |
| npm が信頼するアイデンティティ | 各レジストリエントリの resolved URL は自身のパッケージとバージョンを命名する必要があります |
| 1つの不変条件に1つのホーム | UNSAFE_HUGO_ENV は rebuild-hugo-extended.mjs からインポートされます |
| レッドファースト | 各ハードニングコミットの PR は、最初に失敗させた壊れた入力を記録しています |
| アサーションは修正方法を命名 | allowScripts covers hugo-extended at its locked version X |
| スコープ境界の明示 | 監査のヘッダーコメントに除外対象の領域が明記されています |
| チェックの存在価値 | engines フロアの形状がアサートされ、値はアサートされません。engine-strict がインストール時にそれらを強制します |