ソフトウェアサプライチェーン攻撃が増加する中、自社が利用しているコンポーネントの把握が求められています。SBOM(Software Bill of Materials:ソフトウェア部品表)はその中心的な手段であり、米国大統領令や経済産業省の手引きでも導入が推奨されています。
本記事では、OSS のセキュリティスキャナー Trivy を使って、CircleCI のパイプライン上で SBOM を自動生成する方法を紹介します。SBOM フォーマットはセキュリティ用途として広く使われている CycloneDX を使用します。
本記事のスコープは SBOM の生成と脆弱性スキャンの自動化です。生成した SBOM の管理・配布・サードパーティへの提出については扱いません。
前提条件
- CircleCI のアカウントと対象リポジトリが接続済みであること
-
.circleci/config.ymlを編集できる権限があること - Trivy のイメージは digest で固定します(理由は後述)
なぜ digest で固定するのか
Trivy の Docker イメージに latest タグを使用することは推奨しません。コンテナレジストリ上のタグは mutable であり、同じタグ名のまま中身を差し替えることができます。latest はもちろん、0.69.3 のようなバージョンタグであっても同様です。脆弱性スキャンツール自体がサプライチェーン攻撃の標的になった事例も実際に報告されています。
より安全な方法は、イメージをタグではなく digest(sha256:...)で固定することです。digest はイメージの内容から計算されるハッシュ値であり、レジストリ側で書き換えることができません。同じ digest を指定している限り、取得されるイメージは常に同一であることが保証されます。
digest は以下のコマンドで確認できます。
docker buildx imagetools inspect aquasec/trivy:0.69.3
このコマンドを実行すると、指定したバージョン・タグに対応する情報が表示されます。本記事では執筆時点の 0.69.3 に対応する digest を使用します。バージョンを更新する際は上記コマンドで新しい digest を取得してください。
Name: docker.io/aquasec/trivy:0.69.3
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:bcc376de8d77cfe086a917230e818dc9f8528e3c852f7b1aff648949b6258d1c
Manifests:
Name: docker.io/aquasec/trivy:0.69.3@sha256:7228e304ae0f610a1fad937baa463598cadac0c2ac4027cc68f3a8b997115689
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/amd64
Name: docker.io/aquasec/trivy:0.69.3@sha256:532de2ce287f594fbfbd91853c6d0910517cc87b01501b73b358b3d31b4eb87c
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/arm64
Name: docker.io/aquasec/trivy:0.69.3@sha256:3de26f1d2c6e205d2eeb4e89cb5406c9b522739aba39ca7ac6f3655a85bc1428
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/ppc64le
Name: docker.io/aquasec/trivy:0.69.3@sha256:d9e31ba03445c752ee9cbae9d4074a618ea293811d1b418d5d68fa5226788721
MediaType: application/vnd.docker.distribution.manifest.v2+json
Platform: linux/s390x
出力は2層構造になっています。先頭の Digest: 行が manifest list digest で、4つのアーキテクチャ向けイメージをまとめた「目次」全体を指します。Manifests: 以下の各エントリは、amd64・arm64 など個別アーキテクチャのプラットフォーム digest です。
CircleCI のような Linux/amd64 環境でも、manifest list digest(sha256:bcc376de...)を指定しておくことを推奨します。実行環境のアーキテクチャを自動選択するため、将来 arm64 ランナーへ移行した際にも設定変更が不要です。
Step 1: ファイルシステムをスキャンして SBOM を生成する
最も基本的な構成として、ソースコードのファイルシステムをスキャンして SBOM を生成する例を示します。
trivy fs はディレクトリ内のパッケージマネージャーのマニフェストファイル(package.json、go.sum、requirements.txt 等)を解析し、依存関係を検出します。
version: 2.1
jobs:
generate-sbom:
docker:
# Pin by digest (tags are mutable; digest is immutable)
# Equivalent tag: 0.69.3 — refresh: docker buildx imagetools inspect aquasec/trivy:0.69.3
- image: aquasec/trivy@sha256:bcc376de8d77cfe086a917230e818dc9f8528e3c852f7b1aff648949b6258d1c
steps:
- checkout
- run:
name: Generate SBOM (CycloneDX)
command: |
trivy fs . \
--format cyclonedx \
--output sbom.cdx.json \
--quiet
- store_artifacts:
path: sbom.cdx.json
destination: sbom/cyclonedx.json
workflows:
sbom-pipeline:
jobs:
- generate-sbom
--quiet は必須です。省略すると Trivy がプログレスバーを出力し続け、CircleCI の出力タイムアウト(10 分)でジョブが強制終了します。store_artifacts でアーティファクトとして保存することで、新たな脆弱性が公開された際に過去のリリース物への影響を SBOM から即座に確認できます。
trivy fs --format cyclonedx は SBOM の生成と同時に脆弱性スキャンも実行します。スキャン時点で検出された脆弱性は、生成された SBOM の vulnerabilities フィールドに書き込まれます。脆弱性が存在しない場合は "vulnerabilities": [] となります。ただし、このステップ単体では CI パイプラインのブロック制御(終了コードによるジョブの失敗)は行いません。それは Step 2 で実施します。
npm audit との結果の違いについて
npm audit と Trivy のスキャン結果が一致しない場合があります。これは Trivy がデフォルトで devDependencies をスキャン対象から除外しているためです。vite や rollup といったビルドツールは devDependencies に分類されるため、Trivy のデフォルト設定では検出されません。
devDependencies も含めてスキャンしたい場合は --include-dev-deps フラグを追加します。
- run:
name: Generate SBOM (CycloneDX, including devDependencies)
command: |
trivy fs . \
--format cyclonedx \
--output sbom.cdx.json \
--include-dev-deps \
--quiet
用途に応じて使い分けてください。
| 目的 | 推奨設定 |
|---|---|
| 本番デプロイのリスク評価 | デフォルト(--include-dev-deps なし) |
| CI 環境も含めた包括的なリスク評価 |
--include-dev-deps を追加 |
npm audit に近い結果を得たい |
--include-dev-deps を追加 |
--include-dev-deps フラグは Trivy v0.43.0 で導入されました。npm と Yarn に対応しています。pnpm は v0.54.0 以降のロックファイル(v9 形式)でのみ対応しています。詳細は Trivy の Node.js ドキュメント を参照してください。
Step 2: SBOM から脆弱性スキャンを実行する
生成した SBOM を入力として脆弱性スキャンを実行できます。SBOM を一度生成しておけば、スキャンのたびにファイルシステムを再解析する必要がなくなります。
version: 2.1
jobs:
sbom-and-scan:
docker:
# Pin by digest (tags are mutable; digest is immutable)
# Equivalent tag: 0.69.3 — refresh: docker buildx imagetools inspect aquasec/trivy:0.69.3
- image: aquasec/trivy@sha256:bcc376de8d77cfe086a917230e818dc9f8528e3c852f7b1aff648949b6258d1c
steps:
- checkout
- run:
name: Generate SBOM (CycloneDX)
command: |
trivy fs . \
--format cyclonedx \
--output sbom.cdx.json \
--quiet
- run:
name: Scan SBOM for vulnerabilities
command: |
trivy sbom sbom.cdx.json \
--severity HIGH,CRITICAL \
--quiet \
--exit-code 1
- store_artifacts:
path: sbom.cdx.json
destination: sbom/cyclonedx.json
workflows:
sbom-pipeline:
jobs:
- sbom-and-scan
--exit-code 1 を指定すると、HIGH または CRITICAL の脆弱性が検出された場合にジョブが失敗します。この設定をオン/オフするかは、チームの運用方針に合わせて決定してください。脆弱性検出でパイプラインを止めたくない場合は --exit-code 0 にするか、このフラグを省略します。
trivy sbom の出力には Report Summary が表示され、スキャン対象と検出件数が一覧で確認できます。
Legend の '0': Clean はスキャンされた結果として問題がなかったことを意味し、未スキャンを示す '-' とは区別されます。
--severity で指定できるレベルは UNKNOWN、LOW、MEDIUM、HIGH、CRITICAL です。カンマ区切りで複数指定できます。
Step 3: ワークフローに組み込む
実際の開発フローでは既存のテストジョブと組み合わせます。requires でテスト通過後に SBOM 生成を実行し、filters で main ブランチのみに絞る例を示します。
workflows:
ci:
jobs:
- test
- generate-sbom:
requires:
- test # testジョブ通過後に実行
filters:
branches:
only: main # mainブランチのみ
generate-sbom ジョブ自体は Step 2 の sbom-and-scan をそのまま流用します。
トラブルシューティング
ジョブが 10 分でタイムアウトする
--quiet フラグが抜けているケースがほとんどです。Trivy はデフォルトでプログレスバーを出力しますが、CircleCI の出力監視と相性が悪く、出力がない状態が続くとタイムアウトします。すべての trivy コマンドに --quiet を付けてください。
スキャン結果に依存関係が含まれない
trivy fs は node_modules や .venv 等のロックファイルを解析します。npm ci や pip install を実行する前の状態でスキャンすると、依存関係が検出されないことがあります。checkout 後に依存関係インストールのステップを追加するか、ロックファイル(package-lock.json、poetry.lock 等)がリポジトリに含まれていることを確認してください。
まとめ
CircleCI パイプライン上で Trivy を使って SBOM を自動生成する方法を紹介しました。trivy fs でファイルシステムの SBOM を生成し、trivy sbom で脆弱性スキャンを実行する 2 ステップが基本構成です。
Trivy はデフォルトで devDependencies をスキャン対象から除外します。npm audit と結果が一致しない場合は、この動作の違いを確認してください。CI 環境も含めた包括的なリスク評価が必要な場合は --include-dev-deps フラグを追加します。

