1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OSV-Scanner-MCP v0.22.0: PipenvとGradleで「その脆弱な依存はどこから来たか」がわかるようにしてみた

1
Posted at

Google製OSV-ScannerをMCPサーバーとして使える「OSV-Scanner-MCP」の v0.22.0 をnpmで公開しました。

v0.20.0・v0.21.0では、NuGet・pnpm・Yarn・Bun・uv・Poetry・PDMについて、脆弱な依存が直接の依存か推移的な依存か(dependency_relation)、どのファイルで宣言されたか(declared_in)、どの直接の依存から入ったか(introduced_by)を返すようにしました。
今回は Pipenv(Pipfile.lock) と Gradle(gradle.lockfile) が加わります。

Node.js 22.13以上が必要です

Node.js 20は2026-04-30にサポートが終了したため、対象から外しました。Node.js 22.13以上 が必要です。
22.13は、READMEで説明している--permission(Nodeの権限モデル)が安定版になった最初のNode 22です。CIではNode 22・24・26で試験しています。

Pipenv: Pipfileの宣言から直接の依存を判定する

Pipfile.lockには依存の辺(どのパッケージがどれを必要としているか)がありません。そこで、同じディレクトリの Pipfileの宣言 と突き合わせます。

  • packagesはPipfile.lockのdefaultに、dev-packagesはdevelopに対応させます。独自のカテゴリはその名前のまま扱います
  • Pipfileで宣言され、lockに載っているレジストリのリリースが 直接の依存 です。declared_inはPipfileになります
  • それ以外のlock済みのエントリは、そのカテゴリの宣言がすべてlockと一致し、直接の依存が1つ以上あるときだけ 推移的 とします
  • introduced_byは、そのリリースを宣言せずにlockしているカテゴリのすべてで、入ってきた元が1つに決まるときだけ返します。決まらなければ、部分的に返すのではなく省きます

手書きのPipfileとPipfile.lock(Pipenvは実行していません)で、実際のOSV-Scanner 2.7.0とMCP経由で確かめた結果の抜粋です。

[
  { "name": "requests", "version": "2.25.0", "dependency_relation": "direct", "declared_in": ["Pipfile"] },
  { "name": "urllib3", "version": "1.26.4", "dependency_relation": "transitive", "introduced_by": ["requests"] }
]

jinja2(dev-packages)とpyyaml(独自のカテゴリtools)もPipfileからの直接の依存になり、urllib3・idna・certifiはintroduced_by: ["requests"]の推移的な依存になりました。suggest_fixはrequests 2.33.0、jinja2 3.1.6、pyyaml 5.4、urllib3 2.8.0を推奨しました。

独自のカテゴリもスキャンします

OSV-ScannerはPipfile.lockのうちdefaultとdevelopしか読みません。独自のカテゴリにだけ載っている依存は、そのままでは調べられません。
OSV-Scanner-MCPは、空でない独自のカテゴリを別の一時的なコピーにしてスキャンします。

子プロジェクトの扱い

親のPipfile.lockの下にある子のpyproject.tomlは、次をすべて満たすときだけ「親のlockで確認済み」とします。

  • ルートのPipfileのpath宣言と、lock内の一意のエントリが、子の相対ディレクトリとパッケージ名で一致する
  • 子の静的な実行時の要件がすべて、同じカテゴリに互換なリリースでlockされている

マーカー、extras、動的なメタデータ、空でないoptional・開発用のグループ、古いビルドバックエンドはunconfirmedのままにします。

スキャンのためにビルドを実行しない

Pipenv・Python・プラグイン・ビルドバックエンドは実行しません。
ローカル・Git・URLのエントリはレジストリのリリースとして扱わず、Pipfileに書かれたパスをたどってファイルを読むこともありません。Pipfile.lockは厳密なJSONとして読み(重複キーや128段を超える入れ子は拒否)、1ファイル16 MiB・合計64 MiBまでに制限しています。

Gradle: 信頼できる環境で書き出したグラフを読む

gradle.lockfileには依存の版しかなく、直接・推移的の区別も、どの依存から入ったかもわかりません。それを知るにはGradleで依存を解決する必要があります。しかし、ビルドスクリプトは任意のコードなので、スキャンのためにGradleを実行するのは避けたい ところです。

そこで、v0.22.0では役割を分けました。

  1. 利用者が信頼できるビルド環境で、同梱のinit scriptを使って依存グラフを書き出す
  2. OSV-Scanner-MCPはそのファイルを読むだけ。Gradle・Wrapper・JVMは起動しません
./gradlew -I /absolute/path/to/gradle/export-dependency-graph.init.gradle \
  osvMcpExportDependencies '-PosvMcpConfigurations=*' \
  --no-configuration-cache

gradle/export-dependency-graph.init.gradleはnpmパッケージに同梱しています。Gradle 8.11以上が必要で、8.11.1と9.2.1(JDK 21)で確かめました。GroovyとKotlinのビルドに対応しています。

書き出されたosv-mcp-gradle-dependencies.jsonから、次がわかるようになります。

  • 直接・推移的の区別とintroduced_by: 選択されたコンポーネントとバリアントの辺をたどります。constraintは依存の経路に含めません
  • 古くなったグラフの検出: 評価前の設定ファイル・ビルドファイルを記録して照合するので、ビルドを変えたあとの古いグラフはそのように報告します
  • coverage.dependency_metadata.gradle: 読んだファイル、configuration、完全性、診断の理由を示します

このファイルはOSVには送りません。

declared_inは明示的に有効にしたときだけ

Gradleでは、依存を宣言したファイルは「そのプロジェクトのbuild.gradle」とは限りません。convention pluginやバージョンカタログから入ることもあります。推測でdeclared_inを返すと誤った案内になるため、ビルドの作者が登録した宣言だけ を使い、既定では無効にしています。

対象 有効にする設定
同期的なモジュール宣言(C1a)とProviderの宣言(C1b) OSV_MCP_GRADLE_DECLARATION_TRUST=registered-helper-v1
バージョンカタログのエイリアス・バンドル 上に加えてOSV_MCP_GRADLE_CATALOG_TRUST=local-catalog-inputs-v1
ローカルのbuildSrc・included buildのconvention plugin OSV_MCP_GRADLE_PLUGIN_TRUSTと承認済みのツールチェーンのプロファイル

登録されていない追加が1つでもあれば、そのスコープの宣言の一覧は部分的に返さず、まるごと省きます。また、信頼の設定が不明な値だったり、プロファイルが承認されていなかったりしても、省くのはdeclared_inだけ です。サーバーは止まらず、確かめた直接・推移的の区別とintroduced_byは返します。

suggest_fixの更新案内も、確かめた宣言のパスがあればそれを示し、なければ該当するGradleの宣言やconstraintを見直すよう案内します。

対象の範囲

通常のプロジェクトごとの既定のlockが対象です。buildscriptのlock、独自のlockの場所、included・compositeビルド、Android固有の構成は、まだ検証が足りません。また、precompiledのKotlinスクリプト、標準のkotlin-dsl、公開済みのプラグイン、configuration cache、並列での書き出しには対応していません。

そのほかの修正

  • 同じパッケージが複数のlockfileにあるとき、直接の依存と報告していないファイルのどれかが入ってきた元を示せなければ、introduced_byを省くようにしました。以前は他のファイルの情報だけを、完全なものとして返していました
  • 宣言の一覧が不完全なソースがあるとき、declared_inを省くようにしました。以前は一部のファイルだけを返していました
  • 宣言しているpackage.jsonを確認できないnpmの直接の依存で、更新案内がルートのpackage.jsonを示さないようにしました

導入

Node.js 22.13以上が必要です。Claude Codeの場合:

claude mcp add osv-scanner -- npx -y osv-scanner-mcp

既に使っている方は、npx -y osv-scanner-mcpであれば次の起動時に最新版になります(@0.21.2のように版を固定している場合は更新してください)。Claude Desktop・Codex CLI・VS Code(GitHub Copilot)・Antigravityの設定例はREADMEのSetupにあります。

設定後は、例えば次のように依頼できます。

/absolute/path/to/my-python-app の依存の脆弱性を suggest_fix で調べて、どの直接の依存を更新すればよいか教えてください。

注意点と今後

  • 検出0件は安全の保証ではありません。 必ずcoverageを確認してください
  • Gradleのグラフは、書き出した時点のビルドの結果です。ビルドを変えたら書き出し直してください(古いグラフは報告されます)
  • 実験的な機能として、Gradleの「owned BTA」の診断のための入口があります。既定で無効で、このリリースには承認済みのプロファイルが無いため、結果は出ません
  • Kotlinのコンパイラプラグインの入力などの研究は、検証として続けています。このリリースのサーバーの動作には含まれません

まとめ

PipenvとGradleのプロジェクトでも、脆弱な依存がどこから来たかがわかるようになりました。Gradleでは、スキャンのためにビルドを実行しないことと、推測で宣言元を返さないことを優先しました。確かめられないものは省いて、その理由をcoverageで返します。

不具合や改善案はGitHub Issuesでお知らせください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?