本文へ移動
購読
GUIDE#security更新 2026.09.24

SBOMとは?SyftでSBOMを作りGrypeで脆弱性を照合する

SBOMとは何かを、部品表の中身・形式・最小要件から整理し、SyftでSBOMを作ってGrypeで脆弱性を照合する手順を実行例付きで紹介。VEXでの除外記録と保管・再照合の運用まで。

level
中級
time
約35分
stack
Syft 1.52.0 / Grype 0.119.0 / Ubuntu 24.04
author
@ryoma_t
// 前提条件
Linuxのターミナル操作 / jqの基本 / 脆弱性データベース取得用に約2.1GBの空き容量
SyftでPythonアプリのSBOMを作ってCycloneDX形式で保存し、Grypeで照合して脆弱性を一覧表示したターミナル
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

SBOMとは、ソフトウェアに含まれる部品(ライブラリやOSパッケージ)の名前・バージョン・識別子を、機械が読める形式で並べた「ソフトウェア部品表」です。読み方は「エスボム」です。新しい脆弱性が公表された日に、「自社のどの製品のどの版がそのライブラリを含んでいるか」を、ビルドし直さずに答えるための記録として使います。

このガイドでは、SBOMの中身と形式、最小要件を押さえたうえで、SyftでSBOMを作り、Grypeで脆弱性データベースと照合し、対応しない判断をVEXとして記録するところまでを実際に通します。検証はUbuntu 24.04のコンテナで、Syft 1.52.0とGrype 0.119.0を使い、古いバージョンを固定したPython製のサンプルアプリと、公式の python:3.12-slim イメージを対象にしました。

この記事でわかること

  • SBOMの1行(部品1つ)に何が書かれ、なぜPURLなどの識別子が要るのか
  • SPDXとCycloneDXの違いと選び方、最小要件(NTIAの7項目)と国内外の動き
  • Syftでソースディレクトリとコンテナイメージから作る方法と、部品が漏れる落とし穴
  • Grypeでの照合結果の読み方(FIXED IN・EPSS・RISK)とCIでの止め方
  • 対応しない脆弱性を理由付きで記録する方法(ignore設定とOpenVEX)

SBOMとは:部品表の1行に書かれているもの

SBOMの中身は、部品ごとの記録の集まりです。次は、Syftが作ったCycloneDX形式のSBOMから、テンプレートエンジンのJinja2の記録を取り出したものです(Syft独自の付加情報は省略)。

orders-api-1.4.2.cdx.json(抜粋)
{
  "bom-ref": "pkg:pypi/[email protected]?package-id=cd99a162bf7c2951",
  "type": "library",
  "name": "jinja2",
  "version": "3.1.2",
  "cpe": "cpe:2.3:a:python-jinja2:python-jinja2:3.1.2:*:*:*:*:*:*:*",
  "purl": "pkg:pypi/[email protected]"
}

名前とバージョンだけでは部品を特定できません。同じ「jinja2」でもPythonのパッケージか、OSのパッケージか、別の言語の同名ライブラリかで、該当する脆弱性がまったく違うからです。そこで識別子が2種類付いています。PURL(Package URL)は pkg:pypi/[email protected] のように「どのエコシステムの、どの名前の、どの版か」を1つの文字列で表す形式で、言語のパッケージとはほぼ一意に対応します。CPEは脆弱性データベースNVDが使ってきた製品の識別子ですが、ベンダー名の付け方が揺れるため、Syftはこの1部品に12通りのCPE候補を記録していました。照合の精度はPURLのほうが高く、CPEは補助と考えてください。

SBOMの形式:SPDXとCycloneDX

標準的な形式は2つあり、どちらもJSONで書けます。Syft 1.52.0は、1回のスキャンで両方を同時に出力できました。

観点SPDXCycloneDX
策定団体Linux Foundation(ISO/IEC 5962:2021として国際標準化)OWASP(Ecma InternationalでもECMA-424として標準化)
得意な情報ライセンスと著作権の表記脆弱性の管理(VEXの埋め込み、サービスやAPIの記述)
Syftの出力指定-o spdx-json(検証では SPDX-2.3 で出力)-o cyclonedx-json(検証では specVersion 1.7 で出力)
向いている場面OSSライセンスの確認、法務部門への提出脆弱性の照合、セキュリティ部門や取引先との共有

取引先から形式の指定がなければ、脆弱性管理が目的の社内運用はCycloneDX JSONにそろえるのが扱いやすい選択です。1回のスキャンで両方作れるので、ライセンス確認の依頼が来る可能性があるなら、SPDXも一緒に保管しておけば困りません。なお、GrypeはどちらのJSONも読めます。検証でも、同じ部品表から作ったSPDX版とCycloneDX版で照合結果はどちらも25件でした。

SBOMの最小要件と、国内外の動き

「最低限何が書いてあればSBOMと呼べるか」の基準としてよく参照されるのが、米国NTIAが2021年に示した最小要素です。データ項目は、供給者名、部品名、部品のバージョン、その他の一意な識別子、依存関係、SBOMの作成者、作成日時の7つです。加えて、SPDXやCycloneDXのような機械可読の形式で作ること、リリースのたびに作り直すことも求めています。

ツールが自動で埋められるのは、このうち一部です。検証で requirements.txt から作ったSBOMでは、部品名・バージョン・識別子(PURLとCPE)・作成日時は入っていましたが、SPDX版の部品の供給者は NOASSERTION(不明)、CycloneDX版の作成者(metadata.authors)は空、依存関係は0件でした。requirements.txt には「どのライブラリがどのライブラリを使うか」の情報がないためです。取引先に提出するSBOMは、作成者や製品名を補い、依存関係はロックファイルやビルド後の成果物から取るなど、足りない項目を確かめてから出します。

国内では、経済産業省が「ソフトウェア管理に向けたSBOM(Software Bill of Materials)の導入に関する手引」を2023年に公開し、2024年に改訂しています。EUのサイバーレジリエンス法(CRA)は、デジタル要素を持つ製品の製造者に、少なくとも直接の依存関係を含むSBOMの作成を求めており、主要な義務は2027年12月から適用されます(執筆時点)。国内向けの業務システムでも、取引先の調達条件としてSBOMの提出を求められる場面を想定しておくと、後で慌てずに済みます。

SyftとGrypeのインストール

どちらもAnchore社が開発するオープンソースのツールで、Syftが部品の洗い出し(SBOMの作成)、Grypeが脆弱性データベースとの照合を担当します。公式のインストールスクリプトは、末尾にタグを付けると版を固定できます。CIで使うなら、結果が勝手に変わらないように固定してください。Homebrewの brew install syft grype や、コンテナイメージ anchore/syft・anchore/grype でも使えます。

terminal
# 版を固定してインストール(タグを省略すると最新版)
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sudo sh -s -- -b /usr/local/bin v1.52.0
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin v0.119.0

$ syft version | grep '^Version'
Version:       1.52.0

# 脆弱性データベースを取得する(初回だけ時間がかかる)
$ grype db update
Vulnerability database updated to latest version!
$ grype db status
Path:      /root/.cache/grype/db/6/vulnerability.db
Schema:    v6.1.9
Built:     2026-09-23T06:31:39Z
...
Status:    valid

Grypeのデータベースは展開後で2.1GBあり、検証環境では取得と展開に、2回の計測でそれぞれ1分42秒と2分0秒かかりました。既定では照合のたびに更新を確認し(auto-update: true)、作成から120時間を超えたデータベースでは照合しません(max-allowed-built-age: 120h0m0s)。CIで毎回取得すると時間もディスクも食うので、~/.cache/grype/db をキャッシュしておきます。

SyftでSBOMを作る

対象の指定は dir:(ディレクトリ)、registry:(レジストリ上のイメージ)などの接頭辞で行います。まずはソースディレクトリです。-q は進捗表示を消すオプションで、ログに残すときに付けておくと出力が読みやすくなります。

terminal(検証環境)
$ cat orders-api/requirements.txt
flask==2.2.2
jinja2==3.1.2
requests==2.31.0
urllib3==1.26.18
werkzeug==2.2.2

$ syft scan dir:./orders-api -q -o table
NAME      VERSION  TYPE
flask     2.2.2    python
jinja2    3.1.2    python
requests  2.31.0   python
urllib3   1.26.18  python
werkzeug  2.2.2    python

# 製品名と版を入れ、CycloneDXとSPDXを同時に保存する
syft scan dir:./orders-api -q --source-name orders-api --source-version 1.4.2 \
  -o cyclonedx-json=orders-api-1.4.2.cdx.json \
  -o spdx-json=orders-api-1.4.2.spdx.json

--source-name と --source-version を付けると、CycloneDX版の metadata.component に {"name":"orders-api","version":"1.4.2"} が入り、SPDX版の文書名も orders-api になりました。付けないとディレクトリ名がそのまま入るので、保管したファイルがどの製品のどの版かが中身から判別できなくなります。

バージョンを固定していない依存関係は、ソースディレクトリからのSBOMに載りません。検証で flask>=2.0、requests(指定なし)、jinja2==3.1.2 の3行の requirements.txt をスキャンすると、SBOMに載ったのは jinja2 3.1.2 だけでした。実際に入る版はインストールするまで決まらないためです。SBOMはロックファイル(poetry.lock、uv.lock、package-lock.json など)か、ビルドしたコンテナイメージから作ってください。

コンテナイメージは、registry: を付けるとDockerデーモンなしでレジストリから直接取得して調べます。python:3.12-slim では約10秒で終わり、Debian 13.7のパッケージ87個、Pythonのパッケージ1個(pip)、実行ファイルとして検出したもの7個が出ました。CycloneDX形式にすると、これに加えてイメージ内のファイル2,661個が file 型の部品として記録され、部品数は合計2,757になります。

terminal(検証環境)
syft scan registry:python:3.12-slim -q -o syft-json=py312slim.syft.json -o cyclonedx-json=py312slim.cdx.json

# どのイメージを調べたかは、レジストリ上のダイジェストで残る
$ syft scan registry:alpine:3.20 -q -o syft-json | jq -c '.source | {name, version, repoDigests: .metadata.repoDigests}'
{"name":"alpine","version":"3.20","repoDigests":["index.docker.io/library/alpine@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc"]}

タグ(3.20)は後から別のイメージに付け替えられますが、ダイジェストは中身が同じ限り変わりません。本番に出したイメージのSBOMは、ダイジェストと対にして保管します。

取引先へ渡す前に、SBOMに社内の情報が入っていないかを確かめてください。Syftの出力には部品を見つけたファイルのパス(今回の例では /requirements.txt)が記録され、イメージから作ったCycloneDX版には前述の2,661個のファイルの一覧まで入っていました。repoDigests にはイメージを取得したレジストリのホスト名が残るので、社内レジストリの名前もそのまま載ります。社外向けには、必要な項目に絞ったものを別に作ります。

GrypeでSBOMと脆弱性を照合する

保存したSBOMは sbom: を付けて渡します。イメージやソースを手元に用意し直す必要はありません。

terminal(検証環境・2026年9月23日作成のデータベース)
$ grype sbom:./orders-api-1.4.2.cdx.json -q | head -6
NAME      INSTALLED  FIXED IN  TYPE    VULNERABILITY        SEVERITY  EPSS         RISK
werkzeug  2.2.2      3.0.3     python  GHSA-2g68-c3qc-8985  High      3.4% (88th)  2.5
urllib3   1.26.18    2.6.3     python  GHSA-38jv-5279-wg99  High      3.0% (86th)  2.3
werkzeug  2.2.2      2.2.3     python  GHSA-xg9f-g7g7-2323  High      1.4% (71st)  1.1
flask     2.2.2      2.2.5     python  GHSA-m2qf-hxjv-5gpq  High      1.3% (68th)  1.0
werkzeug  2.2.2      3.0.6     python  GHSA-q34m-jh98-gwm2  Medium    1.1% (64th)  0.7

# CVE番号で表示する(GHSAとCVEの両方があるものはCVEに寄せる)
$ grype sbom:./orders-api-1.4.2.cdx.json -q --by-cve | head -3
NAME      INSTALLED  FIXED IN  TYPE    VULNERABILITY   SEVERITY  EPSS         RISK
werkzeug  2.2.2      3.0.3     python  CVE-2024-34069  High      3.4% (88th)  2.5
urllib3   1.26.18    2.6.3     python  CVE-2026-21441  High      3.0% (86th)  2.3
  • FIXED IN:修正された版。空欄なら、まだ修正版がありません。
  • EPSS:今後30日以内に実際の悪用が観測される確率の推定値で、括弧内は全脆弱性の中での順位(パーセンタイル)です。
  • RISK:深刻度やEPSSなどを組み合わせたGrype独自の指標で、既定の並び順はこの値の大きい順です(執筆時点)。

サンプルアプリでは25件が見つかり、すべてに修正版がありました。OSパッケージを多く含むイメージでは事情が違います。python:3.12-slim のSBOMを照合すると、High 50件・Medium 63件・Low 10件・Negligible 45件が出ましたが、--only-fixed(修正版があるものだけ)を付けると、High 1件・Medium 12件・Low 2件・Negligible 1件まで減りました(執筆時点のデータベースでの値で、日々変わります)。残りの多くは、配布元がまだ修正を出していないか、修正しない判断をしたものです。すぐに手を動かせるのは修正版があるものなので、日々の確認は --only-fixed から始め、修正版のないものは影響の有無を判断して記録します。

CIで止めるときは --fail-on を使います。指定した深刻度以上が1件でもあると、終了コードが2になります。検証では grype sbom:./orders-api-1.4.2.cdx.json -q --only-fixed --fail-on high の終了コードは2でした。修正版のないものでビルドが止まり続けないよう、--only-fixed と組み合わせるのが実用的です。

対応しない判断を記録する:ignore設定とVEX

照合結果には、実際には影響しないものも混ざります。そのたびに頭の中で除外するのではなく、理由を付けてファイルに残し、結果から外します。社内だけで使うならGrypeの設定ファイル、取引先にも「影響しない」と伝えたいならVEX(Vulnerability Exploitability eXchange)の形式が向いています。

.grype.yaml
ignore:
  # 例: requests の verify=False 関連。使っていないので影響なし。次回の更新で解消予定
  - vulnerability: GHSA-9wx4-h78v-vm56
    package:
      name: requests
    reason: "verify=False を使っておらず影響なし。次回更新で解消予定"
orders-api.vex.json(OpenVEX)
{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://example.jp/vex/orders-api-1.4.2",
  "author": "Example Corp 情報システム部",
  "timestamp": "2026-09-23T00:00:00Z",
  "version": 1,
  "statements": [
    {
      "vulnerability": { "name": "CVE-2024-34069" },
      "products": [ { "@id": "pkg:pypi/[email protected]" } ],
      "status": "not_affected",
      "justification": "vulnerable_code_not_in_execute_path",
      "impact_statement": "Werkzeugのデバッガは本番で起動しない構成"
    }
  ]
}
terminal(検証環境)
# 設定ファイルで除外したものは、--show-suppressed で理由付きの除外として確認できる
$ grype sbom:./orders-api-1.4.2.cdx.json -q -c .grype.yaml --show-suppressed | grep 9wx4
requests  2.31.0     2.32.0    python  GHSA-9wx4-h78v-vm56  Medium    0.3% (27th)  0.2    (suppressed)

# VEXで not_affected とした脆弱性は、通常の結果から消える
$ grype sbom:./orders-api-1.4.2.cdx.json -q --by-cve --vex orders-api.vex.json --show-suppressed | grep CVE-2024-34069
werkzeug  2.2.2      3.0.3     python  CVE-2024-34069  High      3.4% (88th)  2.5    (suppressed by VEX)

VEXの status には、影響なし(not_affected)、影響あり(affected)、修正済み(fixed)、調査中(under_investigation)があり、影響なしとするには理由(justification)を選んで書きます。除外は「なかったこと」にする操作ではないので、誰がいつ判断したかが残る形式を選び、半年に1回など期限を決めて見直します。

SBOMの保管と、毎日の再照合

SBOMの価値が出るのは、作った日ではなく、新しい脆弱性が公表された日です。リリースごとに 製品名-版.cdx.json の名前で、イメージのダイジェストと一緒に成果物の保管場所へ置き、本番で動いている版の分は消さないようにします。そのうえで、保管したSBOMを毎日、更新したデータベースで照合し直します。ビルドは不要で、SBOMの数だけ grype sbom: を回すだけです。

「特定のライブラリを含む製品はどれか」という問い合わせには、jqでSBOMを横断検索して答えられます。検証環境で保管した2つのSBOMから openssl と urllib3 を探すと、どのSBOMにどの版が入っているかが1行ずつ出ました。

terminal(検証環境)
$ for f in *.cdx.json; do
    jq -r --arg f "$f" '.components[]? | select(.name=="openssl" or .name=="urllib3") | "\($f)\t\(.name)\t\(.version)"' "$f"
  done
orders-api-1.4.2.cdx.json	urllib3	1.26.18
py312slim.cdx.json	openssl	3.5.7-1~deb13u2

すでにTrivyで定期スキャンを回しているなら、SBOMの作成と再照合もTrivyでできます。SyftとGrypeは、作る道具と照合する道具が分かれているぶん、「SBOMを成果物として保管し、あとから照合する」という流れが手順の上でも明確になるのが利点です。どちらかに寄せて、同じSBOMを2つのツールで二重に管理しないようにします。

動作確認

ここまでの手順が通っているかは、次の4点で確かめます。

terminal
# 1. SBOMの形式・版・部品数
jq -r '.bomFormat, .specVersion, (.components|length)' orders-api-1.4.2.cdx.json
# 検証環境: CycloneDX / 1.7 / 6(5つのライブラリと requirements.txt 自体)

# 2. 製品名と版が入っているか
jq -c '.metadata.component | {name, version}' orders-api-1.4.2.cdx.json
# {"name":"orders-api","version":"1.4.2"}

# 3. データベースが新しいか(Status: valid、Built が5日以内)
grype db status

# 4. 照合とCIでの判定
grype sbom:./orders-api-1.4.2.cdx.json -q --only-fixed --fail-on high; echo "exit=$?"
# 修正版のあるHigh以上が残っていれば exit=2

つまずきポイント

SBOMに入っているはずのライブラリが出てこない

ソースディレクトリを対象にしたとき、バージョンを固定していない依存関係は記録されません。検証でも flask>=2.0 と requests は出ませんでした。ロックファイルを置くか、依存関係をインストールしたコンテナイメージを registry: や docker: で指定してスキャンします。

「WARN convert is an experimental feature」

syft convert で形式を変換すると表示されます(Syft 1.52.0)。変換先の形式にない情報は落ちることがあるので、両方の形式が要るなら、スキャン時に -o を2つ並べて同時に出力するほうが確実です。

CIのたびにデータベースの取得で数分待たされる

展開後2.1GBのデータベースを毎回取得しているのが原因です。~/.cache/grype/db をCIのキャッシュに入れ、日付をキーにして1日1回だけ更新します。インターネットに出られない環境では、取得したアーカイブを grype db import で取り込みます。作成から120時間を超えると照合しない設定が既定なので、キャッシュの更新が止まっていないかも確かめます。

修正版のない脆弱性でビルドが止まり続ける

--fail-on だけを付けると、配布元が修正を出していないOSパッケージの脆弱性でも終了コード2になります。--only-fixed を組み合わせ、残りは影響を判断して .grype.yaml かVEXに理由付きで記録します。

よくある質問

SBOMとは何ですか?

ソフトウェアを構成するライブラリやパッケージの名前・バージョン・識別子を、SPDXやCycloneDXといった機械可読の形式で列挙した部品表です。読み方は「エスボム」。新しい脆弱性が公表されたとき、影響を受ける製品と版を、ビルドし直さずに特定するために使います。

SBOMを導入するメリットは何ですか?

脆弱性の公表から「どこが該当するか」を答えるまでの時間が、保管したSBOMの検索と再照合だけで済むほど短くなります。加えて、取引先からの提出要求への対応、OSSライセンスの確認、使っているライブラリの古さの把握にも同じ記録を使えます。効果が出るのは、リリースごとに作って保管する運用にしてからです。

SBOMツールは無料で利用できますか?

作成と照合だけなら無料で始められます。このガイドで使ったSyftとGrype、Trivyはオープンソースで、脆弱性データベースも無償で取得できます。複数の製品のSBOMを一元管理する画面、承認のワークフロー、ライセンスの詳細な分析などが必要になったら、商用の管理サービスを検討します。

SBOMの最小要件は何ですか?

よく参照される米国NTIAの最小要素では、供給者名・部品名・バージョン・その他の一意な識別子・依存関係・SBOMの作成者・作成日時の7項目を求めています。ツールの出力だけでは供給者や作成者が空になることがあるので、提出前に中身を確認し、足りない項目を補ってください。

まとめ

SBOMは、PURLなどの識別子付きで部品を並べた記録で、形式はCycloneDXかSPDXを使います。Syftではロックファイルかビルド後のイメージを対象にし、製品名と版を入れて保存します。Grypeでは --only-fixed で手を付けられるものから片付け、影響しないものは .grype.yaml やVEXに理由付きで残します。リリースごとに保管したSBOMを毎日照合し直す流れができれば、新しい脆弱性の公表日に「うちは該当するか」をその日のうちに答えられるようになります。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。