Google製OSV-ScannerをMCPサーバーとして使える「OSV-Scanner-MCP」の v0.12.0 をnpmで公開しました。
前回のv0.11.0で、Java・JavaScript・Python・Goの依存をまとめてスキャンできるようになりました。今回は Rust(Cargo.lock) を加えました。
使い方は変わりません
scan_projectにプロジェクトのディレクトリを渡すと、Cargo.lockも他の言語のlockfileと一緒に検出してスキャンします。
{
"project_path": "/absolute/path/to/project"
}
| エコシステム | 対応ファイル |
|---|---|
| Java(Maven) |
pom.xml、gradle.lockfile
|
| JavaScript(npm) |
package-lock.json、npm-shrinkwrap.json、yarn.lock、pnpm-lock.yaml、bun.lock
|
| Python(PyPI) |
poetry.lock、uv.lock、Pipfile.lock、pdm.lock、requirements.txt
|
| Go | go.mod |
| Rust(crates.io) | Cargo.lock(lockfileの形式v1〜v4) |
これまでと同じく、cargoは実行しません。
lockfileを読むだけです。
osv-scannerにCargo.lockをそのまま渡すと誤検知する
実装前に、osv-scanner v2.4.0に手書きのCargo.lockを渡して挙動を確認しました。すると、Cargo.lockのsource(どこから来たcrateか)を見ずに、すべてのパッケージをcrates.ioのcrateとして照会することが分かりました。
| crateの種類 |
Cargo.lockのsource
|
osv-scanner単体の挙動 |
|---|---|---|
| 自分のcrate(workspaceのメンバー・pathの依存) | なし | crates.ioとして照会 |
| gitの依存(forkなど) | git+https://... |
crates.ioとして照会 |
| 社内レジストリのcrate | registry+https://社内/... |
crates.ioとして照会 |
試しに、ローカルのcrateをfailure 0.1.8、gitのforkをsmallvec 1.6.0、社内レジストリのcrateをatty 0.2.14という名前と版にしたところ、3つともcrates.ioの同名crateの脆弱性が報告されました。中身はまったく別物なので誤検知です。しかも、社内crateの名前がapi.osv.devへ送られます。
そこでOSV-Scanner-MCPでは、元のCargo.lockはosv-scannerに渡しません。本サーバーが解析し、sourceがcrates.ioのパッケージだけを書いたコピーをスキャンします(cargo auditと同じ考え方です)。
{
"coverage": {
"complete": false,
"local_crates": 2,
"unscannable_crates": [
{
"file": "Cargo.lock",
"name": "gitdep",
"version": "0.3.0",
"reason": "Git dependency: its version does not correspond to a crates.io release, so it was not checked against crates.io advisories"
}
]
}
}
| 項目 | 意味 |
|---|---|
local_crates |
スキャンしなかった自分のcrateの数。自分のコードなので、completeには影響しません |
unscannable_crates |
スキャンしなかったgitの依存・社内レジストリのcrateと理由。第三者のコードが未検査なので、complete: falseになります。sourceのURLは応答に含めません |
解析は、cargoが書き出す形式だけを受け付ける厳格なものにしました。
想定外の行が1つでもあれば、ファイル全体をスキャン対象から外して理由を示します。一部だけ読んで「取りこぼしたのに0件」となるのを避けるためです。
[replace]で置き換えたcrateも扱います。置き換え前のcrates.ioのcrateは実際には使われないので、スキャンしません(osv-scanner単体では置き換え前の版を照会します)。
「保守終了」は脆弱性と分けて数える
Rustの脆弱性データベースであるRustSecには、脆弱性ではない 情報提供のアドバイザリ があります。
| 種類 | 意味 |
|---|---|
unmaintained |
crateの保守が終了している |
unsound |
健全性(soundness)の欠陥がある |
notice |
その他の通知 |
osv-scannerはこれらも脆弱性と同じように返します。多くは修正版がありません(保守終了は版を上げても解消しないため)。そのまま数えると、脆弱性の件数が水増しされ、LLMが「修正版のない脆弱性がある」と要約しかねません。
そこで、情報提供のアドバイザリにはinformationalを付け、vulnerability_countではなく informational_count に数えるようにしました。実際の応答では次のようになります(atty 0.2.14)。
[
{
"id": "RUSTSEC-2021-0145",
"severity": "unknown",
"summary": "Potential unaligned read"
},
{
"id": "RUSTSEC-2024-0375",
"severity": "unknown",
"informational": "unmaintained",
"summary": "`atty` is unmaintained"
}
]
1件目のRUSTSEC-2021-0145はRustSecではunsound(情報提供)ですが、GitHubのアドバイザリデータベースが同じ欠陥を脆弱性として登録しています。このように 別のデータベースが脆弱性として扱っているものは、脆弱性のまま数えます。関連するIDの詳細が欠けていて分類を確かめられない場合も、脆弱性側に倒しています。
lockfileが無いcrateも知らせる
ライブラリのcrateは、Cargo.lockをリポジトリに含めない慣習が長く続いていました。Cargo.lockの無いCargo.tomlは、応答のcoverage.lockfile_missingに「cargo generate-lockfileを信頼できる環境で実行してから再スキャン」という案内付きで示します。
workspaceでは、メンバーのCargo.tomlはサブディレクトリにあり、Cargo.lockはルートにだけあります。メンバーかどうかは、ルートのCargo.tomlの[workspace]のmembers・excludeで確認します。globの展開やexcludeの扱いは、cargo 1.98の実際の挙動に合わせました。
導入
Node.js 20.19以上が必要です。Claude Codeの場合:
claude mcp add osv-scanner -- npx -y osv-scanner-mcp
既に使っている方は、npx -y osv-scanner-mcpであれば次の起動時に最新版になります(@0.11.0のように版を固定している場合は更新してください)。Claude Desktop・Codex CLI・VS Code(GitHub Copilot)・Antigravityの設定例はREADMEのSetupにあります。
設定後は、例えば次のように依頼できます。
/absolute/path/to/my-rust-appの依存の脆弱性をscan_projectで調べてください。スキャンできなかったcrateと、保守終了のcrateがあれば分けて教えてください。
通信先について
Cargo.lockのスキャンで通信するのは api.osv.devだけ です(プロキシで接続先を記録して確認しました)。送られるのはcrates.io由来のcrateの名前と版で、自分のcrate・gitの依存・社内レジストリのcrateの名前は送りません。
注意点と今後
-
検出0件は安全の保証ではありません。 必ず
coverageを確認してください -
suggest_fix(修正版の推奨)は、Rustにはまだ対応していません(unsupported_ecosystemを返します)。次のv0.13.0で、推奨版、直接依存・推移的依存の区別、cargo update -p <crate>@<版> --precise <推奨版>による更新方法の案内に対応する予定です - workspaceの外にあるpathの依存は、実際にはルートの
Cargo.lockに含まれていても、同名の別crateと区別できないためlockfile_missingにunconfirmedとして示します
まとめ
- OSV-Scanner-MCPがRustのプロジェクトにも使えるようになりました。
- osv-scannerに
Cargo.lockを渡すだけでは、自分のcrateやgitの依存がcrates.ioのcrateと取り違えられます。それを防ぎ、スキャンできなかったものはcoverageで隠さずに返すことを重視しました。
不具合や改善案はGitHub Issuesでお知らせください。