ソフトウェア依存関係の「深さ」を徹底比較:Mavenはnpmの5倍以上?10大エコシステムの実証研究
公開日
パッケージ管理システムを利用してライブラリを1つ追加した際、意図せず数十個ものパッケージが裏側でインストールされる経験をしたことはありませんか?現代のソフトウェア開発において、こうした「見えない依存関係」の連鎖は避けられないものであり、同時にセキュリティ上の重大なリスク要因ともなっています。特に「npmは依存関係が複雑で深い」というイメージが広く持たれていますが、果たしてそれはデータに基づいた事実なのでしょうか。
本記事では、オーバーン大学の研究者Jahidul Arafat氏による論文「How Deep Does Your Dependency Tree Go? An Empirical Study of Dependency Amplification Across 10 Package Ecosystems」(2025年)に基づき、主要な10のパッケージエコシステムにおける依存関係の実態を解説します。500のプロジェクトを対象とした詳細な調査データを紐解くことで、言語ごとのリスクの違いと、開発者が取るべき対策を明らかにします。
調査の概要:10の主要エコシステムを解析
本研究では、Java、JavaScript、Rust、Pythonなど、現代のソフトウェア開発を支える10の主要なパッケージエコシステムを対象に、依存関係の構造が調査されました。各エコシステムから人気のある50プロジェクトずつ、合計500プロジェクトが抽出され、その依存関係ツリーが解析されています。
調査対象のエコシステムと特徴
調査対象となったエコシステムと、それぞれの特徴は以下の通りです。これらは異なる言語ファミリー、プラットフォーム、設計思想を代表しています。
図表1:データセットとエコシステムの特徴
| エコシステム | 分析PJ数 | 平均直接依存数 | 平均推移的依存数 | マニフェスト形式 |
|---|---|---|---|---|
| Maven | 50 | 5.4 | 74.9 | XML |
| npm | 50 | 30.9 | 52.8 | JSON |
| Cargo | 50 | 13.7 | 15.1 | TOML |
| PyPI | 50 | 4.2 | 6.1 | Python |
| NuGet | 50 | 3.9 | 7.7 | XML |
| RubyGems | 50 | 4.7 | 12.1 | Ruby |
| Go Modules | 50 | 7.7 | 27.3 | Go |
| Packagist | 50 | 12.2 | 12.2 | JSON |
| CocoaPods | 50 | 0.5 | 0.7 | Ruby |
| Pub | 50 | 7.5 | 15.8 | YAML |
依存関係増幅(Dependency Amplification)とは
本研究の核となる概念が「依存関係増幅」です。これは、開発者が直接宣言したパッケージ数に対して、実際にインストールされるパッケージ(推移的依存関係を含む)が何倍に膨れ上がるかを示す指標です。
- 直接依存(Direct Dependencies): プロジェクトのマニフェストファイル(
package.jsonやpom.xmlなど)に明記されたパッケージ。 - 推移的依存(Transitive Dependencies): 直接依存しているパッケージがさらに依存しているパッケージ。
- 増幅率(Amplification Factor): 推移的依存数 ÷ 直接依存数(最小値1で正規化)。
例えば、たった1つのライブラリを追加しただけで、そのライブラリが依存する多くのパッケージが連鎖的に依存関係として追加される現象を指します。
【結果】常識を覆す依存関係の「増幅」実態
調査の結果、エコシステム間で依存関係の増幅パターンに劇的な違いがあることが判明しました。「npmは依存関係が深い」という一般的な通説とは異なるデータが示されています。
Mavenの増幅率はnpmの5倍以上
最も驚くべき発見は、Javaのパッケージ管理システムであるMavenの増幅率が平均24.70倍に達し、他のエコシステムを圧倒している点です。これに対し、JavaScriptのnpmは平均4.32倍に留まりました。
図表3:10エコシステムにおける依存関係の特性(平均値)
| エコシステム | 直接依存数 | 推移的依存数 | 総依存数 | 増幅率(倍) |
|---|---|---|---|---|
| Maven | 5.4 | 74.9 | 80.3 | 24.70× |
| Go Modules | 7.7 | 27.3 | 34.9 | 4.48× |
| npm | 30.9 | 52.8 | 83.7 | 4.32× |
| RubyGems | 4.7 | 12.1 | 16.8 | 4.32× |
| Pub | 7.5 | 15.8 | 23.2 | 2.61× |
| NuGet | 3.9 | 7.7 | 11.6 | 2.32× |
| PyPI | 4.2 | 6.1 | 10.2 | 1.50× |
| Packagist | 12.2 | 12.2 | 24.5 | 1.24× |
| Cargo | 13.7 | 15.1 | 28.8 | 0.97× |
| CocoaPods | 0.5 | 0.7 | 1.2 | 0.32× |
このデータは、Mavenプロジェクトにおいては、開発者が1つのパッケージを追加するごとに、平均して約25個ものパッケージが裏側で追加されることを意味します。一方で、Cargo(Rust)やPyPI(Python)、CocoaPodsなどは非常に低い増幅率を示しており、宣言した数と実際にインストールされる数が近い、あるいはそれ以下(システム依存などが多いため)であることがわかります。
Mavenは「全体的に」深く、npmは「外れ値」が深い
Mavenとnpmの違いは、平均値だけでなくその分布にも表れています。Mavenは全体の28%のプロジェクトが増幅率10倍を超えており、エコシステム全体として依存関係が深くなる傾向があります。
対照的に、npmは平均的な増幅率はMavenより低いものの、最大値(外れ値)が極端です。一部のプロジェクトでは数百倍の増幅が見られますが、多くのプロジェクトは比較的浅い依存関係に収まっています。
図表4:10エコシステムにおける依存関係増幅の分布
Mavenの箱ひげ図が高い位置にあり、外れ値も多い一方、CargoやCocoaPodsは非常に低い位置で安定している様子が視覚化されています。
【リスク】見えない依存関係が招くアタックサーフェスの拡大
依存関係の増幅は、そのまま「アタックサーフェス」の拡大を意味します。直接管理していない推移的依存パッケージに脆弱性が見つかれば、プロジェクト全体がリスクに晒されるからです。
アタックサーフェスの最大値と平均値
各プロジェクトが最終的に信頼しなければならないコードの総量(総パッケージ数)を見ると、平均値ではMaven(80.3個)とnpm(83.7個)は同程度です。しかし、最大値を見るとnpmは785個に達し、極端なケースでのリスクが高いことがわかります。
図表5:アタックサーフェス(総インストールパッケージ数)の統計
| エコシステム | 平均値 | 最大値 | P95(95パーセンタイル) | 増幅率10倍以上の割合 |
|---|---|---|---|---|
| npm | 83.7 | 785 | 250.6 | 12% |
| Maven | 80.3 | 450 | 397.2 | 28% |
| Go Modules | 34.9 | 263 | 133.6 | 6% |
| Cargo | 28.8 | 189 | 132.4 | 0% |
| PyPI | 10.2 | 57 | 30.2 | 0% |
| CocoaPods | 1.2 | 12 | 7.0 | 0% |
この表から、Maven環境では約3割のプロジェクトが高い増幅率を示しており、日常的に深い依存関係ツリーのリスクに晒されていることが読み取れます。一方、CargoやPyPI、CocoaPodsなど5つのエコシステムでは、増幅率が10倍を超えるプロジェクトは0%でした。
図表6:直接依存数と総依存数の関係
Mavenが急勾配の直線を描き、少ない直接依存で多くの総依存を生む傾向がある一方、npmはばらつきが大きいことが示されています。
【要因】なぜこれほどの差が生まれるのか
なぜエコシステムによってこれほど劇的な違いが生まれるのでしょうか。論文では、以下の3つの要因が指摘されています。
1. エコシステムの設計思想と文化
Maven(Java)は、Spring Frameworkのように「単一の依存関係で包括的な機能を提供する」フレームワーク文化が根付いています。開発者の利便性は高いものの、巨大な依存ツリーが不可避となります。 一方、npm(JavaScript)は「1つのパッケージは1つのことだけをうまくやる(Unix哲学)」という文化があり、小さなパッケージを多数組み合わせる傾向があります。これが高い直接依存数(平均30.9個)と、特定のパッケージにおける極端な推移的依存を生む要因となっています。
2. 標準ライブラリの充実度
Python(PyPI)やGo言語は「Battery Included(バッテリー同梱)」の哲学を持ち、標準ライブラリが非常に充実しています。基本的な機能のために外部パッケージに依存する必要が少ないため、結果として増幅率が低く抑えられています(PyPI: 1.50倍, Go: 4.48倍)。
3. プラットフォームの制約
CocoaPods(iOS/macOS開発)が最も低い増幅率(0.32倍)を記録したのは、Appleのプラットフォーム制約によるものです。機能の大部分はOSが提供するシステムフレームワークから供給されるため、外部パッケージへの依存が極めて少なくなります。
結論:開発者が取るべきアクション
本研究の結果は、使用する技術スタックによってセキュリティ対策のアプローチを変える必要があることを示唆しています。
Maven(Java)環境の場合
Mavenは 「構造的に依存関係が深くなる」 傾向があります。直接依存の数だけでリスクを判断することは危険です。
- 対策:
dependency:treeなどのコマンドを活用し、推移的依存関係を含めた完全な監査を定常的に行う必要があります。SBOM(ソフトウェア部品表)の生成と管理が特に重要になる領域です。
npm(JavaScript)環境の場合
npmは平均的にはMavenほど深くありませんが、 「極端な外れ値」 が存在します。
- 対策: プロジェクトにパッケージを追加する際、そのパッケージが極端に深い依存関係を持っていないか個別に確認することが有効です。問題のある「外れ値」パッケージを特定し、回避することでリスクを大幅に低減できます。
その他の環境(Rust, Python, PHPなど)
Cargo、PyPI、Packagistなどは、比較的制御された依存関係を保っています。
- 対策: 現状の標準的なセキュリティプラクティスを維持しつつ、エコシステムが成熟するにつれて増幅率が変化しないか注視することが推奨されます。
「npmは依存関係が複雑」というイメージにとらわれず、実際のデータに基づいた適切なリスク管理を行うことが、サプライチェーンセキュリティを確保する第一歩となります。
Webサービスや社内のセキュリティにお困りですか? 弊社のサービス は、開発チームが抱える課題を解決し、生産性と幸福度を向上させるための様々なソリューションを提供しています。ぜひお気軽にご相談ください!
参考資料: