SBOM
SBOMは出荷したビルドを記述すべきです
ビルド時SBOMは、ビルドの実行中に、実際にビルドへ流れ込んだ通信から記録されます。一方、多くのSBOM生成ツールはロックファイルや成果物から後でリストを再構成するため、ビルドが独自に取得する隠れた依存関係を見落とします。その理由と、ビルドを記録することで何が変わるのかを解説します。
更新日
Build SBOM、Source SBOM、Analyzed SBOM
CISAのSBOMの種類に関するガイダンスでは、SBOMをいつ、どのように作成するかで分類しています。そのうち3種類で、現在使われているほぼすべてのSBOM生成ツールをカバーできます。
| 種類 | 作成方法 | 見落としうるもの |
|---|---|---|
| Source | ソースファイル、マニフェスト、ロックファイルから作成(通常はSCAツールによる) | ランタイム、プラグイン、プラットフォームのコンポーネント、ファイルに宣言されていないもの全般 |
| Build | リリース可能な成果物を作るビルドの一部として作成 | ビルド環境から見えないもの。だからこそ完全性を明示すべきです |
| Analyzed | ビルド後に、実行ファイル、パッケージ、コンテナイメージを解析して作成 | ツールが認識できないコンポーネント。結果はヒューリスティクスに依存します |
CISAはBuild SBOMの利点を2つ挙げています。ビルドとCI/CDプロセスで得られる情報があるため、SBOMが成果物を正しく表しているという確信が高まること。そして、同じビルドワークフローでSBOMと成果物の両方に署名できるため、信頼性が高まることです。欠点として挙げられているのは、生成のためにビルドプロセスの変更が必要になる場合があることです。CRACIなら、その変更はruns-on: craciの1行だけで、記録はランナーに組み込まれています。
フォルダーのスキャンとビルドの記録
多くのSBOM生成ツールはスキャナーです。ソースツリーやコンテナイメージを指定すると、マニフェスト、ロックファイル、インストール済みパッケージ、バイナリを読み取り、何が含まれたかを推定します。これは有用であり、自社でビルドしていないソフトウェアについては唯一の選択肢であることも少なくありません。
弱点は、スキャナーには目の前にあるものしか見えないことです。ロックファイルがコミットされていない場合、スキャン前に依存関係がインストールされていない場合、あるいは製品がコンパイル済みバイナリとして出荷される場合、スキャナーが扱える情報はビルド時よりも少なくなります。コンポーネントをひとつも見つけられなかったスキャンでも、有効なSBOMドキュメントは生成されます。ここが多くの人が見落とす点です。
CRACIは異なるアプローチを取ります。独自のCIランナー内でSBOMを生成するため、出荷される成果物と記述される成果物が一致します。近くのどこかで実行されたスキャンではありません。
SBOM basics: SPDX, CycloneDX and OpenVEX
SBOMが何を記録するのか、SPDXとCycloneDXの形式が実際にどう違うのか、そして脆弱性が製品に実際に影響するかどうかについてOpenVEXが何を加えるのかを解説します。
Kubernetes Community Days Helsinki 2026でのPetteri Pulkkinen、Erika Marttinen、SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chainsの16:50~22:05。
スキャンが依存しているもの
Syft、Trivy、cdxgen、Microsoft SBOM Tool、GitHubの依存関係グラフのエクスポート、Socket CLIは、いずれも読み取れるものからSBOMを導き出します。以下のパターンは、これらのツールのドキュメントに記載された動作から導かれるものです。
何をコンポーネントとみなすかはツールによって異なる
各生成ツールは、何をコンポーネントとみなし、どのファイルを信頼するかを独自に判断します。そのため、同じ固定コミットからでも、ツールごとに異なるリストが得られます。
その一部は命名の問題です。ツールは同じGitHub Actionを異なるパッケージURLタイプで識別するため、同じものを記載していても一致しないことがあります。これも問題の一部です。コンポーネントに異なる識別子を使うSBOMは、脆弱性データとの照合が難しくなります。
ツールの違いより、先にインストールしたかどうかが重要になることも
Express 5.2.1はロックファイルをコミットしていません。クリーンなチェックアウトを対象にすると、ロックファイルやインストール済みツリーから依存関係を解決する生成ツールはそのどちらも手にできず、package.jsonで宣言されたnpmの依存関係はひとつもドキュメントに載りません。それでも、GitHub Actionsやそれを参照するワークフローファイルなど、他に認識できたものでリストを返すことはあります。ここに壊れているツールはありません。
同じリポジトリをインストールしたコピーでは、Syftのフラグひとつで答えがまた変わります。同じツール、同じツリー、同じバージョンです。Syftのディレクトリスキャンは、設計上、プロジェクトが宣言しているものを報告します。インストール済みパッケージを読み取るカタロガーは、ディレクトリに対するデフォルトのセットに含まれていません。
読み取るべきマニフェストがないソフトウェアもある
v2.1.259時点のanthropics/claude-codeリポジトリには、どのような種類の依存関係マニフェストもありません。package.jsonも、ロックファイルも、それに相当するものもありません。各生成ツールが返す結果は他の種類のファイルから組み立てられたものであり、その答えはプロジェクトよりもツールについて多くを物語ります。
公開されているnpmパッケージもあまり変わりません。@anthropic-ai/claude-codeはランタイム依存関係をゼロと宣言し、ビルド済みバイナリをラップするプラットフォーム固有のパッケージを8つ宣言しています。@openai/codex 0.149.1も同じ構成です。開発者のマシンで実行されるのはコンパイル済みバイナリであり、マニフェストを読むツールにはその中身が見えません。これは特定のツールの欠陥ではありません。事後のスキャンでは記述できない配布モデルなのです。
バージョンとサプライヤー
2つのツールが同じパッケージを見つけても、バージョンが一致するとは限りません。あるツールはGitHub ActionをコミットSHAで固定し、別のツールはリリースタグを報告することがあります。誤ったバージョンをキーにした脆弱性の検索は、自信に満ちた誤答を返します。
NTIAの最小要素のひとつであるサプライヤーフィールドは、空のまま出力されることがよくあります。ほとんどの生成ツールには、その情報源がないためです。
隠れた依存関係:どのスキャンにも見えないもの
上記のツールはいずれも、ロックファイルやビルドの成果物を起点に、何が含まれたかを推定します。しかしビルドはそれ以上のものを取得します。こうした隠れた依存関係はどのロックファイルにも記載されないため、スキャンでは見つけられません。
-
インストールフック。npmの
preinstallやpostinstallスクリプトはインストール中に実行され、さらにコードをダウンロードできます。Shai-Huludワームもこの仕組みで実行されました。 - ビルドスクリプト。Rustのビルドスクリプトなどは、必要なものをビルド時にダウンロードします。
- ベースイメージ。コンテナのビルドはベースイメージとそのOSレイヤーを取り込みますが、言語のロックファイルにはどれも記載されません。
- キャッシュとビルドツール。CIキャッシュから復元されたパッケージや、セットアップ手順でインストールされたツールも、ロックファイルの外から入ってきます。
後から成果物をスキャンしても、このギャップは埋まりません。完成したバイナリ、とりわけRustのようなコンパイル済みコードでは、何が含まれたかをスキャナーが推測するしかありません。
2回のビルド、2つのSBOM
スキャンがビルドの代わりにならない理由はもうひとつあります。ほとんどのビルドは再現可能ではないのです。同じコミットを続けて2回ビルドしても、同じパッケージが得られる保証はありません。パッケージマネージャーはレスポンスが届いた順に解決とインストールを行います。これは多くのPythonプロジェクトのようにロックファイルがない場合に特に影響が大きく、さらにビルド手順そのものにランダム性が含まれることもあります。Nixなどのツールを使えばバイト単位で再現可能なビルドも実現できますが、まれです。
つまり、後から別のマシンや別のビルドで生成したSBOMは、一度も出荷されなかったビルドを記述している可能性があります。本番環境に何があるかを証明できるのは、リリースを生成したCIビルドの最中に記録されたSBOMだけです。
ビルドを記録すると何が変わるか
CRACIには通信が見えますが、スキャナーには見えません。CRACIはお客様のGitHub Actionsジョブを独自のランナーで実行し、各ビルドに流れ込むネットワーク通信を把握します。そのためSBOMには、マニフェスト上で必要とされるものではなく、推移的依存関係や隠れた依存関係を含め、ジョブが実際に取り込んだものが記載されます。
ビルド時の記録が難しくなるのがキャッシュです。キャッシュから復元されたパッケージはパッケージレジストリにアクセスしないため、プロキシだけでは見落とします。CRACIは依存関係のエビデンスをキャッシュと一緒に引き継ぐため、キャッシュから復元されたパッケージも、それを復元したジョブのSBOMに残ります。 GitHub Actionsのキャッシュが隠すものをご覧ください。
検出対象のエコシステムには、npm、PyPI、RubyGems、Cargo、Go、Nix、OCI、apt、apk、Gitの各ソースに加え、YoctoとOpenEmbeddedのダウンロードプリセットが含まれます。JFrog ArtifactoryのnpmとPyPIのミラーにも対応しています。宣言ライセンスのメタデータもエクスポートに含まれます。SBOMはCycloneDXとSPDXでエクスポートできます。機能の全体像は SBOM生成のページで紹介しています。
通信が見えることは、逆方向にも役立ちます。ビルドに必要なのは既知のソースからのパッケージであり、成果物の公開先も既知の場所です。CRACIのエグレスポリシーはそれらだけを許可し、それ以外の外向き接続はすべて即座にブロックします。そのため、シークレットを攻撃者のサーバーに送信しようとするマルウェアは失敗します。このポリシーは既知の悪性ホストを探すのではなくビルドに必要なものを許可する方式のため、Shai-Huludのように、誰も見たことのない攻撃の最初の標的になった場合でも有効です。 エグレスポリシーが情報流出を防ぐ仕組みをご覧ください。
コンテナとDockerのSBOM
違いが最も表れるのはコンテナイメージです。DockerのSBOMを得る一般的な方法は、いずれもイメージを解析します。SyftとTrivyはイメージをスキャンし、docker scout sbomはイメージにSBOMアテステーションがない場合にその内容をインデックス化します。--sbom=trueで有効にするBuildKitのSBOMアテステーションは、デフォルトでSyftベースのスキャナーを最終イメージステージに対して実行します。旧来のdocker sbomプラグインは非推奨となり、docker scout sbomに置き換えられています。
これらはいずれも完成したイメージを読み取るため、レイヤー内でまだ識別できるものしか見えません。ビルドの途中で取得されたものは見えないのです。CRACIはイメージをビルドするジョブを記録します。ジョブがOCIレジストリやパッケージソースから取り込むイメージとパッケージは、ビルドの他の部分と同じSBOMに記録されます。自社でビルドしていないイメージについては、引き続きスキャンが現実的な選択肢です。DockerイメージのSBOMが見落とすものをご覧ください。
ジョブごとに示す完全性
記録されたSBOMの質は、記録の質で決まります。CRACIはすべてのSBOMを完全なものとして示すのではなく、ジョブとキャッシュごとに、5つの状態のいずれかで完全性を明示します。
- Complete。CRACIがジョブの完全なエビデンスを観測し、復元されたすべてのキャッシュも完全でした。
- Complete with connections。要件は満たしていますが、ジョブが1つ以上のカスタムネットワーク接続を使用したため、その接続を通じて入ってきたものを確認する価値があります。
- Incomplete。依存関係がSBOMから漏れる可能性のある状況をCRACIが特定しました。
- Unavailable。CRACIがエビデンスの評価を完了できませんでした。
- Not recorded。そのジョブは完全性レポート機能の導入前に実行されたものです。
完全性はキャッシュをまたいで推移的に引き継がれます。ジョブが不完全なキャッシュを復元すると、そのSBOMも不完全となり、そのジョブが生成するキャッシュにも不完全の状態が引き継がれます。信頼できるSBOMと再確認が必要なSBOMを見分けられます。
The hard parts: hooks, noise and staying current
講演で指摘する3つの課題です。どのマニフェストにも載っていないものを取得するpost-installフック、長い一覧の中から重要な依存関係を見つけること、そしてSBOMを最新に保つこと、つまりすべてのビルドでCI内でSBOMを生成することです。
Kubernetes Community Days Helsinki 2026でのPetteri Pulkkinen、Erika Marttinen、SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chainsの25:39~27:22。
APIによる来歴の提供
SBOMは、CRACIが各ジョブについて記録するものの一部です。APIは、同じビルドから記録されたSBOM、ネットワークトレース、来歴を返します。
スキャナーが今も適しているケース
ビルド時の記録が対象とするのは、自社でビルドするソフトウェアです。自社でビルドしていないサードパーティのイメージやバイナリについては、引き続きスキャナーが現実的な選択肢であり、上記のツールは高機能で広く使われています。スキャナーが悪いと言いたいのではありません。自社のリリースのSBOMは、それを作ったビルドから得るべきだということです。
CRACIのSBOMにも限界があります。対象はCRACIランナー上で実行されるジョブであり、現時点ではLinux上のGitHub Actionsです。その他のCIシステムはロードマップに掲載されています。
ツールを比較検討中ですか?SBOMツールの比較をご覧いただくか、ビルド時SBOMがCRAコンプライアンスの取り組みにどう役立つかをご確認ください。Syft、Trivy、cdxgenの比較は、オープンソースのSBOM生成ツールが見落とすものをご覧ください。
ビルド時SBOMに関するよくあるご質問
ビルド時SBOMとは何ですか?
ビルド時SBOM(CISAの呼称ではBuild SBOM)は、リリース可能な成果物をビルドする過程で生成されます。後からファイルをもとにコンポーネントのリストを再構成するのではなく、ビルド自体が使用したものを記述します。CRACIは、各GitHub Actionsジョブが実際に取得したパッケージからこれを記録します。
Build SBOMとAnalyzed SBOMの違いは何ですか?
Analyzed SBOMは、ビルド後に実行ファイルやコンテナイメージなどの成果物を解析して生成され、一般にヒューリスティクスが必要です。Build SBOMは、ビルドの最中にビルドが持つ情報から生成されます。Analyzed SBOMはビルドへのアクセスがなくても作成できるため、自社でビルドしていないソフトウェアに適しています。
DockerイメージのSBOMはどのように生成しますか?
既存のイメージについては、Syft、Trivy、docker scout sbomなどの生成ツールでスキャンします。BuildKitも、ビルド時に最終イメージをスキャンしてSBOMアテステーションを添付できます。イメージのビルドが取り込んだものを記録するには、そのビルドをCRACIランナーで実行してください。ジョブのSBOMには、取得したイメージとパッケージが含まれます。
隠れた依存関係とは何ですか?
ビルドが取得するもののうち、どのロックファイルにも記載されないものすべてです。npmのインストールフックがダウンロードするコード、Rustのビルドスクリプトがビルド時にダウンロードするファイル、コンテナビルドにおけるベースイメージとそのOSレイヤーなどです。ロックファイルを読むツールや成果物をスキャンするツールは、これらを見落とします。CRACIはビルドに流れ込む通信が見えるため、これらを記録します。
同じコードを2回ビルドすると、なぜ異なるSBOMになるのですか?
ほとんどのビルドは再現可能ではないためです。パッケージマネージャーは、特にロックファイルがない場合、レスポンスが届いた順にパッケージを解決してインストールします。また、ビルド手順にランダム性が含まれることもあります。同じコミットを続けて2回ビルドすると、2回目のビルドに異なるパッケージが含まれることがあります。リリースに何が含まれているかを証明できるのは、そのリリースを生成したビルドの最中に記録されたSBOMだけです。
ビルド時SBOMは常に完全ですか?
自動的に完全になるわけではありません。記録の質は観測できる範囲で決まるため、CRACIはすべてのジョブとキャッシュについて、Complete、Complete with connections、Incomplete、Unavailable、Not recordedのいずれかで完全性を明示します。信頼できるSBOMと再確認が必要なSBOMを見分けられます。
ビルド時SBOMはSBOMスキャナーに取って代わりますか?
自社でビルドするソフトウェアについては、そのとおりです。リリースのSBOMは、それを作ったビルドから得るべきです。自社でビルドしていないサードパーティのイメージやバイナリについては、引き続きスキャナーが現実的な選択肢です。選択肢についてはSBOMツールの比較をご覧ください。
CRACIはどのSBOM生成ツールを使っていますか?
どれも使っていません。CRACIはファイルをスキャンしません。ランナー上のパッケージを認識するプロキシが、各ジョブがnpm、PyPI、RubyGems、Cargo、Go、Nix、OCI、apt、apk、Gitの各ソース、およびYoctoとOpenEmbeddedのダウンロードプリセットから取得したパッケージを記録し、その結果をCycloneDXまたはSPDXでエクスポートします。