Python開発に潜む脆弱性が14万以上のパッケージへ拡散-依存関係が引き起こすサプライチェーンリスクの実態

セキュリティニュース

投稿日時: 更新日時:

Python開発に潜む脆弱性が14万以上のパッケージへ拡散-依存関係が引き起こすサプライチェーンリスクの実態

2025年8月に公開された論文「PyPitfall: Dependency Chaos and Software Supply Chain Vulnerabilities in Python」は、Pythonエコシステムにおける依存関係の複雑さと、それに起因するサプライチェーンリスクやセキュリティリスクの深刻さを明らかにしました。

この調査では、Pythonのパッケージ管理システムであるPyPIに登録された37万件以上のパッケージを対象に、依存性に潜む脆弱性がどのように伝播し、どのような形でユーザーの開発環境に影響を与えるかを詳細に分析しています。

調査概要:PyPitfallとは

「PyPitfall」は、PyPIに登録された37万8,573個のパッケージを対象に、直接および間接(トランジティブ)依存性を解析し、それらが既知の脆弱性を含むバージョンに依存していないかを評価した研究です。

主な調査結果は次の通りです。

  • 4,655件のパッケージが、既知の脆弱性を含むバージョンを明示的に「必須」として指定。

  • 14万1,044件のパッケージが、脆弱性を含むバージョンを「許容」しており、依存解決の過程でそのバージョンが選ばれる可能性がある。

  • Pythonのパッケージ依存関係の平均深度は2.3層だが、脆弱な依存性はさらに深い階層(平均4〜6層)に存在していることが多い。

  • 循環依存が100万件以上存在し、依存解決の障害にもなっている。

問題の根源:「依存関係のカオス」

Pythonのパッケージエコシステムでは、多くのパッケージが他のパッケージに依存して動作します。この構造そのものは再利用性と生産性の向上をもたらしますが、依存が多層構造化することで、管理不能なセキュリティリスクを招きます。

調査では、以下のような脆弱性の根本原因が明らかになっています:

  1. トランジティブ依存性(間接的な依存)の不可視性
  2. 脆弱なバージョンの許容(Permissive Versioning)
  3. 循環依存(Cyclic Dependency)
  4. メンテナンスの停滞
  5. pipの依存解決仕様の限界

以下、それぞれの問題を具体的に解説します。

原因1:トランジティブ依存性の不可視性

トランジティブ依存性とは、自分が直接インストールしたパッケージがさらに依存しているパッケージを指します。例えば、あるWebアプリケーションでFlaskを使っていた場合、Flask自体は他にも複数のライブラリ(Jinja2, Werkzeugなど)に依存しており、それらがさらに別のパッケージに依存しています。

この”依存の連鎖”が2次、3次と深くなるにつれ、開発者自身がどのようなコードやライブラリを実際に自分の環境へ取り込んでいるかの把握が困難になります。その結果、意図しない脆弱性コードがプロジェクトに紛れ込む危険性が高まります。

研究では、脆弱なパッケージの平均的な依存階層は4〜6層に存在しており、可視化されにくい場所に脆弱性が潜んでいることが示されました。

原因2:脆弱なバージョンの許容(Permissive Versioning)

パッケージのバージョン指定において、開発者が”>=1.0.0″や”^2.3.1″のように”緩やかな制約”を設けている場合、その後に公開されたセキュリティホールを含むバージョンが自動的にインストール対象になる可能性があります。

調査では、14万件以上のパッケージが脆弱性を含むバージョンを許容する記述になっていることが判明しました。これは、意図せず安全でないコードがインストールされる主な原因の一つです。

また、他者の書いた依存先コードにおいてこのような緩い指定がなされていた場合、それを利用するだけで脆弱性が伝播するというドミノ効果が発生します。

原因3:循環依存の存在

研究では、100万件を超える循環依存の組み合わせが確認されています。循環依存とは、パッケージAがパッケージBに依存し、BがCに依存し、最終的にCがAに依存する、といった無限ループ構造です。

このような構造は依存関係の解決を複雑にするだけでなく、パッケージの更新やメンテナンスを妨げる要因ともなります。そして何より、古いバージョンを使い続けざるを得ない状況を作り出し、そこに含まれる脆弱性が放置される温床になります。

原因4:メンテナンスの停滞

PyPIには、既にメンテナンスされていない、あるいはメンテナの失踪によって放置されたパッケージが数多く存在します。こうした”メンテナンス孤児”のパッケージが脆弱性を含んでいても、修正が入らないまま利用され続けることがしばしばあります。

本来であれば置き換えやフォークが推奨されるべきですが、依存が複雑な場合には代替が困難であり、結果として古いまま使い続ける状況が続いてしまいます。

原因5:pipの依存解決仕様と限界

Pythonのパッケージ管理ツールであるpipは、基本的に最小限の衝突回避と依存解決しか行いません。これはパフォーマンスのためには妥当ですが、セキュリティという観点から見ると大きな制約になります。

例えば、ある依存パッケージが複数の依存先とバージョン競合を起こしている場合、pipは最終的に”どれか一つ”を選ぶだけで、最も安全なものを選ぶ保証はありません。また、ユーザーに詳細な警告を出す仕組みも乏しく、脆弱性のあるバージョンが選ばれても開発者は気づかないケースが多いのです。

結論:依存性の見直しと可視化が急務

PyPitfallの研究は、Pythonエコシステムにおける脆弱性が、個別のパッケージのバグではなく、”構造的な依存関係の管理不全”によって生じていることを示しました。特に、企業や組織の情報システム部門がこの問題に対応するためには、以下のような対策が求められます:

  • 依存関係の定期的な棚卸しと可視化(ツール:pipdeptree, pip-audit)
  • CI/CDへの脆弱性スキャンの組み込み
  • トランジティブ依存性のチェックを含むセキュリティポリシーの策定
  • 古い/メンテナンスされていない依存の代替検討

ソフトウェアサプライチェーンの健全性を保つには、ただコードを書くのではなく、その裏で支えるエコシステム全体の安全性を設計・管理する姿勢が必要です。

参照

https://arxiv.org/pdf/2507.18075