メインコンテンツまでスキップ

既知脆弱性への対応

ここで扱う「既知脆弱性」は、CVE だけを指しません。 CVE、GHSA、OSV ID、ベンダー独自の advisory ID など、脆弱性の識別子には複数の形式があります。 Takumi Images では、特定の ID 体系だけに依存せず、スキャナや上流 advisory が報告する脆弱性 ID と、その実際の影響を照合して判断します。

脆弱性 ID が付与された既知の問題が Takumi イメージから解消される経路は、3つに絞られています。 上流の新しいリリースへ追従する経路、上流にすでにある修正を先取りして当てる経路、そして実際には影響を受けない、あるいは誤検知だと判断した場合に VEX で表明する経路です。 どの経路をたどるかは、その脆弱性の状況によって決まります。

経路使う場面効果が及ぶ範囲
pin の前進上流が修正版をすでにリリースしているそのパッケージを使うすべてのイメージに一括で届く
パッケージ単位のパッチ修正は上流にあるが、pin にはまだ入っていないそのパッケージが現れるすべての箇所(直接でも依存経由でも)に適用される
VEX による not_affected の表明実際には影響を受けない、または誤検知だと判断した根拠つきで、該当する検出結果だけに適用される

上流リリースへの追従

パッケージに修正が入る主な経路は、ビルドの土台にしている固定した単一のソースリビジョン(pin)を、新しい版へ進めることです。 pin はカタログ全体で共有しているので、進めるのは 1 回で済み、そのパッケージを使うすべてのイメージにまとめて修正が届きます。 たとえば、多くのイメージが土台にしている共有ライブラリの修正版が上流から出れば、pin を 1 回進めることで、そのライブラリを積むイメージすべてが新しい版になります。

この経路は、高い頻度で繰り返します。 pin を進めるたびに、影響するすべてのイメージを再ビルドし、テストをやり直したうえで再スキャンします。 そのため、修正が届くタイミングは、上流のリリース頻度と、この再ビルドの頻度の両方に左右されます。

パッケージ単位の先取りパッチ

修正そのものは上流にすでに存在するのに、pin にはまだ取り込まれていない、ということがあります。 リリースがまだ出ていない場合や、リリースはあってもまだ pin を進めていない場合です。 このときは、そのパッケージだけに当てる小さな修正(overlay と呼びます)を使い、pin の更新を待たずに修正を反映します。 overlay の実体は、対象パッケージの定義に上流のパッチを重ねる短い Nix 式です。

final: prev: {
curl = prev.curl.overrideAttrs (old: {
patches = (old.patches or []) ++ [ ./patches/fix-header-injection.patch ];
});
}

パッチファイルには、上流でコミットされた修正を使用します。 overlay もまた式なので、どのパッケージに何を当てたかは、ビルドの入力として記録に残ります。

適用したパッチは、そのイメージの SBOM にも pedigree.patches として diff ごと記録されます。 たとえば公開中の busybox イメージの SBOM には、busybox 1.37.0 に対して先取り適用した CVE-2025-60876(wget の HTTP ヘッダ注入)や CVE-2024-58251(netstat の出力サニタイズ)に対応する修正 diff が記録されています。 どのバージョンに何を当てた状態のビルドなのかを、弊社の説明ではなく署名付きの SBOM で確認できます。

overlay は、当てた先のパッケージが現れるすべての箇所に適用されます。 あるイメージに直接含まれる場合でも、別のパッケージの依存関係を経由して間接的に含まれる場合でも同じです。 一度当てれば、次に pin を進めたときにその修正が上流側に吸収されるまで、そのパッケージを使うイメージ全体に修正が及びます。

修正版がまだ上流からリリースされていない深刻な脆弱性については、社内の基準に沿って判断したうえで、この経路によるバックポートを検討することがあります。 ただし、これはケースごとの判断であって、すべての既知脆弱性に対してあらかじめ約束しているものではありません。

VEX による not_affected の表明

スキャンのパイプラインは、見落としを避けるため、疑わしい検出結果を広めに報告します。 そのため、報告される検出結果のなかには、実際には該当しないものが含まれます。 争いのある(disputed)脆弱性レコードや、バージョン表記は変わらないが実際には修正済みのコードを使っている場合、そのコード経路自体をイメージが含んでいない場合などです。

こうした検出結果は、機械が読める VEX(Vulnerability Exploitability eXchange)のステートメントとして、not_affected と表明します。 ステートメントには、判断の根拠になる impact statement を添えます。 どの検出結果が対象で、なぜ該当しないと判断したのかが、後から確認できる形で残ります。 この記録は追記のみで運用しており、状況が変わったときは前の記述を書き換えず、新しい日時のステートメントを足します。 実際に未修正のものは affected のまま残るので、この表明は、件数を少なく見せるためのものではありません。

VEX の形式と、署名付き attestation としての検証方法は VEX を参照してください。

スキャンの鮮度

スキャンは、pin を進めて再ビルドするたびに実行し直します。 そのため、「スキャン結果がクリーンである」というのは、そのイメージが持つ恒久的な性質ではなく、直近に再ビルドしたビルドについての性質です。 新しい脆弱性は日々公開されるので、同じ digest を固定したまま使い続けると、時間とともに検出結果は増えていきます。

したがって、この仕組みが前提にしているのは、:latest のようなタグを定期的に pull し直す運用です。 digest でデプロイ対象を固定しつつ、同時に古い digest を使い続けないようにする、という両立が必要になります。

pin の前進や overlay が公開までのどの段階で適用されるかは、ビルドパイプラインの概要を参照してください。 この仕組みが利用者の作業をどう減らすかは、脆弱性対応の省力化で説明しています。