理系学生日記

おまえはいつまで学生気分なのか

syftとgrypeでJavaとNode.jsプロジェクトの依存関係と脆弱性を可視化する

最近、オープンソースソフトウェアを利用したサプライチェイン攻撃が増加しています。そのため、プロジェクトの依存関係管理と脆弱性対策の重要性が高まっています。僕は開発プロジェクトで多くの依存パッケージを利用していますが、その依存関係の可視化と潜在的な脆弱性の検知が必要だと感じていました。

今回は、syftとgrypeという2つのツールを使って、JavaプロジェクトのPOM(pom.xml)とNode.jsプロジェクトのpackage.jsonから依存関係を可視化し、脆弱性を検知する方法を紹介します。

はじめに

なぜSBOMが重要か

ソフトウェアの依存関係は年々複雑化しています。直接的な依存だけでなく、間接的な依存も含めると、一つのプロジェクトで数百から数千のパッケージを利用することも珍しくありません。この状況で、以下のような課題が発生しています:

  • 利用しているパッケージの全容把握が困難
  • 脆弱性が発見された際の影響範囲の特定に時間がかかる
  • ライセンスコンプライアンスの確認が複雑

これらの課題に対応するため、SBOMが注目されています。SBOMとは「Software Bill of Materials」の略で、ソフトウェアの構成要素を一覧化したものです。食品の原材料表示のように、ソフトウェアの「材料」を明確にすることで、セキュリティとコンプライアンスの管理を効率化できます。

本記事で使用するツール

今回使用する2つのツールの役割は以下の通りです:

  1. syft:依存関係の可視化とSBOMの生成
  2. grype:生成されたSBOMを基にした脆弱性スキャン

これらのツールを組み合わせることで、継続的なセキュリティ管理が可能になります。

syftとgrypeとは

まず、syftは、Anchore社が開発したソフトウェアコンポーネントの依存関係を分析し、SBOMを生成するツールです。以下のような特徴があります:

  • 多様なパッケージマネージャーのサポート(Maven、npm、etc)
  • 複数のフォーマットでのSBOM生成
    • JSON:プログラムでの処理に最適
    • SPDX:標準化されたフォーマットで、ツール間の互換性が高い
    • CycloneDX:セキュリティ分析に特化した詳細な情報を含む
  • コンテナイメージの解析にも対応
  • 実行が高速

また、grypeもAnchore社が開発した脆弱性スキャンツールで、syftと連携して動作します。主な特徴は以下の通りです:

  • 最新の脆弱性データベースを活用
    • National Vulnerability Database (NVD)
    • GitHub Security Advisories
    • その他、各言語エコシステムの脆弱性データベース
  • 重要度に基づく脆弱性の分類(Critical、High、Medium、Low)
  • 豊富な出力フォーマット
  • 高速な脆弱性スキャン

ローカルでの環境構築

まず、syftとgrypeをローカルPCにインストールします。

# syftのインストール
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

# grypeのインストール
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin

インストールが完了したら、バージョンを確認します。

$ syft version
Application: syft
Version:    1.20.0
BuildDate:  2025-02-21T20:31:16Z
GitCommit:  Homebrew
GitDescription: [not provided]
Platform:   darwin/arm64
GoVersion:  go1.24.0
Compiler:   gc

$ grype version
Application:         grype
Version:             0.87.0
BuildDate:           2025-01-22T20:31:08Z
GitCommit:           Homebrew
GitDescription:      [not provided]
Platform:            darwin/arm64
GoVersion:           go1.23.5
Compiler:            gc
Syft Version:        v1.19.0
Supported DB Schema: 5

Javaプロジェクト(pom.xml)の解析

まず、手元のJavaプロジェクトの依存関係を解析してみましょう。以下は、nablarch-coreプロジェクトを例にしています。

cd /Users/kiririmode/src/github.com/nablarch/nablarch-core
syft pom.xml

このコマンドを実行すると、以下のような形式でプロジェクトの依存関係が表示されます。

 ✔ Indexed file system    pom.xml
 ✔ Cataloged contents     edd2b8dd54f1c8190d6e78966c59d78d0380cc917d896769b347f2067e547caa
   ├── ✔ Packages                        [4 packages]
   ├── ✔ File digests                    [1 files]
   ├── ✔ File metadata                   [1 locations]
   └── ✔ Executables                     [0 executables]
NAME                    VERSION  TYPE
json-path-assert        2.4.0    java-archive
mockito-core            UNKNOWN  java-archive
nablarch-core           2.2.0    java-archive
nablarch-slf4j-adaptor  UNKNOWN  java-archive

より詳細な情報をSBOMで出力します。例えば、SPDX形式で出力し、ソフトウェアコンポーネント(packages)の一部情報を表示します。

$ syft -q pom.xml -o spdx-json | jq -r '.packages[] | [ .name, .SPDXID, .versionInfo ] | @csv '

"json-path-assert","SPDXRef-Package-java-archive-json-path-assert-b9f8b5dad3d5ab1d","2.4.0"
"mockito-core","SPDXRef-Package-java-archive-mockito-core-b1a15a6e93dab83d","UNKNOWN"
"nablarch-core","SPDXRef-Package-java-archive-nablarch-core-20c66a65499e2f90","2.2.0"
"nablarch-slf4j-adaptor","SPDXRef-Package-java-archive-nablarch-slf4j-adaptor-f3cc12b003c1b302","UNKNOWN"
"pom.xml","SPDXRef-DocumentRoot-File-pom.xml","sha256:3b110735d9a8c1b259d3b123e286e4ce48c876012e6e4d2fd8c9020a65c84e12"

親POMからの依存関係の解決

一部の依存関係でUNKNOWNというバージョンが表示されています。これは、syftが親POMを参照できていないことが原因です。例えば、mockito-coreの依存定義を見てみましょう。

    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-core</artifactId>
      <scope>test</scope>
    </dependency>

バージョンが指定されていないのは、親POMで管理されているためです。実際にmvn dependency:treeで確認すると、以下のようにバージョンが解決されています。

mvn dependency:tree | sed -n '/dependency/,/----/p'
[INFO] --- dependency:3.7.0:tree (default-cli) @ nablarch-core ---
[INFO] com.nablarch.framework:nablarch-core:jar:2.2.0
[INFO] +- org.mockito:mockito-core:jar:5.3.0:test
[INFO] |  +- net.bytebuddy:byte-buddy:jar:1.14.4:test
[INFO] |  +- net.bytebuddy:byte-buddy-agent:jar:1.14.4:test
[INFO] |  \- org.objenesis:objenesis:jar:3.3:test
[INFO] +- com.jayway.jsonpath:json-path-assert:jar:2.4.0:test
[INFO] |  +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] |  |  \- net.minidev:json-smart:jar:2.3:test
[INFO] |  |     \- net.minidev:accessors-smart:jar:1.2:test
[INFO] |  |        \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] |  +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] |  +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] |  \- org.slf4j:slf4j-api:jar:1.7.25:test
[INFO] +- com.nablarch.integration:nablarch-slf4j-adaptor:jar:2.1.0:test
[INFO] +- junit:junit:jar:4.13.1:test
[INFO] +- org.hamcrest:hamcrest-all:jar:1.3:test
[INFO] \- org.jacoco:org.jacoco.agent:jar:runtime:0.8.8:test
[INFO] ------------------------------------------------------------------------

syftでもこれらのバージョンを正しく解決するには、2つの方法があります:

ローカルのMaven Cacheを参照する方法

SYFT_JAVA_USE_MAVEN_LOCAL_REPOSITORY=true syft pom.xml

ネットワーク経由でPOMを取得する方法

SYFT_JAVA_USE_NETWORK=true syft pom.xml

これらの環境変数は公式ドキュメントには記載されていませんが、GitHubのIssueで確認できます:

どちらの方法を使っても、以下のように正しいバージョンが表示されます:

$ diff -u <(syft -q pom.xml) <(SYFT_JAVA_USE_NETWORK=true syft -q pom.xml)
--- /dev/fd/11 2025-03-03 14:25:27
+++ /dev/fd/12 2025-03-03 14:25:28
@@ -1,5 +1,5 @@
 NAME                    VERSION  TYPE
 json-path-assert        2.4.0    java-archive
-mockito-core            UNKNOWN  java-archive
+mockito-core            5.3.0    java-archive
 nablarch-core           2.2.0    java-archive
-nablarch-slf4j-adaptor  UNKNOWN  java-archive
+nablarch-slf4j-adaptor  2.1.0    java-archive

脆弱性の検知

続いて、grypeを使って脆弱性をスキャンします。まず、syftでSBOMを生成し、それをgrypeに渡します。

$ SYFT_JAVA_USE_NETWORK=true syft -q pom.xml -o cyclonedx-json=nablarch-core.cyclonedx.json
$ grype -q nablarch-core.cyclonedx.json
No vulnerabilities found

nablarch-coreには脆弱性は存在していないようです。これは良いニュースですが、実際のプロジェクトでは脆弱性が見つかることも多いでしょう。

Node.jsプロジェクト(package.json)の解析

Node.jsプロジェクトの解析では、まずpackage.jsonを試してみましょう。

$ cd /Users/kiririmode/src/github.com/Fintan-contents/promptis
$ syft -q package.json
No packages discovered

パッケージが検出されません。パッケージが検出されない理由はおそらく、JavaScriptパッケージを解析するためのカタログが適切に選択されていないためです。JavaScriptパッケージカタログを明示的に指定してみましょう。

$ syft -q package-lock.json --select-catalogers '+javascript-package-cataloger' | head
NAME                             VERSION  TYPE
@tootallnate/quickjs-emscripten  0.23.0   npm
agent-base                       7.1.1    npm
ast-types                        0.13.4   npm
balanced-match                   1.0.2    npm
basic-ftp                        5.0.5    npm
brace-expansion                  2.0.1    npm
data-uri-to-buffer               6.0.2    npm
debug                            4.3.7    npm
degenerator                      5.0.1    npm

catalogersの仕組み

syftは、ファイルの種類や内容に応じて適切なカタログ(解析プラグイン)を自動選択します。カタログには以下のような種類があります:

  • 言語固有のカタログ(Java、Node.js、Python等)
  • パッケージマネージャー固有のカタログ(npm、Maven、pip等)
  • コンテナ関連のカタログ(Docker、OCI等)

--select-catalogersオプションを使うと、特定のカタログを明示的に指定できます。利用可能なカタログは以下のコマンドで確認できます:

$ syft cataloger list -o json | jq -r '.catalogers[].name'
alpm-db-cataloger
apk-db-cataloger
binary-classifier-cataloger
bitnami-cataloger
cargo-auditable-binary-cataloger
...(省略)...

脆弱性の検知

package-lock.jsonの依存関係に対して脆弱性スキャンを実行します:

$ syft -q package-lock.json --select-catalogers '+javascript-package-cataloger' -o cyclonedx-json=promptis.cyclonedx.json
$ grype -q promptis.cyclonedx.json
No vulnerabilities found

このプロジェクトでも脆弱性は見つかりませんでした。では、実際に脆弱性がある例を見てみましょう。

脆弱性検知の実例

このBlogプロジェクトのファイルシステム上のファイルにある依存パッケージをスキャンしてみます:

$ syft . -qo cyclonedx-json | grype -q
NAME      INSTALLED  FIXED-IN  TYPE    VULNERABILITY        SEVERITY
black     23.12.1    24.3.0    python  GHSA-fj7x-q9j7-g6q6  Medium
jinja2    3.1.2      3.1.5     python  GHSA-q2x7-8rv6-6q7h  Medium
jinja2    3.1.2      3.1.4     python  GHSA-h75v-3vvj-5mfj  Medium
jinja2    3.1.2      3.1.3     python  GHSA-h5c8-rqwp-cp95  Medium
jinja2    3.1.2      3.1.5     python  GHSA-gmj6-6f8f-6699  Medium
werkzeug  3.0.1      3.0.3     python  GHSA-2g68-c3qc-8985  High
werkzeug  3.0.1      3.0.6     python  GHSA-q34m-jh98-gwm2  Medium
werkzeug  3.0.1      3.0.6     python  GHSA-f9vj-2wh5-fj8j  Medium

ここでは複数の脆弱性が検出されました。特に注目すべき点は:

  1. 重要度の違い(High vs Medium)
  2. 同一パッケージでの複数の脆弱性(jinja2)

実践的な使用方法

grypeは脆弱性の重要度を以下の4段階で表示します:

  • Critical:直ちに対応が必要
  • High:優先的に対応が必要
  • Medium:計画的に対応
  • Low:状況に応じて対応

特定の重要度以上の脆弱性が存在する場合にコマンドを失敗させることもできます:

$ syft . -qo cyclonedx-json | grype -q --fail-on high > /dev/null
$ echo $?
1

この機能は、CIパイプラインでの品質ゲートとして活用できます。

まとめ

syftとgrypeを活用することで、以下のメリットが得られます:

  • プロジェクトの依存関係の可視化が容易に
  • 潜在的な脆弱性を素早く発見可能
  • 脆弱性の重要度に基づく対応の優先順位付けが可能
  • 定期的なセキュリティチェックの自動化

JavaとNode.jsのプロジェクトでは、数多くの依存パッケージを使用することが一般的です。これらのツールを活用して、プロジェクトのセキュリティ管理を効率的に行いましょう。