osv-scanner-mcp 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -220,7 +220,8 @@ api.osv.dev と deps.dev はどちらも Google が運営するサービスで
220
220
  - `package-lock.json`(v2以降): ルートとworkspaceのpackage.jsonの依存を、Nodeの解決規則(入れ子の `node_modules` から上位へ)で解決したものが直接依存、そこからたどれるものが推移的依存です。直接依存には宣言しているpackage.jsonを `declared_in` に、推移的依存にはそれを要求している直接依存の名前を `introduced_by`(最大10件、超えた分は `introduced_by_omitted`)に示します。直接依存でもあり他の依存からも要求される版は `direct` とし、`introduced_by` も付けます
221
221
  - `go.mod`: `// indirect` の無い `require` が直接依存です。`replace` で置き換えているモジュールには `replaced_in_go_mod: true` を付けます(OSV-Scannerは置換先のパスと版で報告します)
222
222
  - `requirements.txt`: ファイルに書かれた依存が直接依存、deps.devで解決された依存が推移的依存です
223
- - 上記以外の形式(`pom.xml`、`gradle.lockfile`、`yarn.lock`、`pnpm-lock.yaml`、`bun.lock`、`poetry.lock`、`uv.lock`、`Pipfile.lock`、`pdm.lock`)と、lockfileVersion 1・どこからも要求されていないエントリは `unknown` です。複数のlockfileで判定が異なる場合は `mixed` です
223
+ - `pom.xml`: OSV-Scannerは、`pom.xml`(と親POM)に宣言された依存と、deps.devで解決された推移的依存を別々の結果(`source.type` が `lockfile` / `unknown`)に分けて報告するため、それで判定します(親POM・プロファイル・プロパティ・依存管理の解釈はOSV-Scannerと同じになります)。`introduced_by` / `declared_in` は付きません。この判定はOSV-Scannerの文書化されていない出力の形に依存するため、想定外の形の場合は `unknown` にします
224
+ - 上記以外の形式(`gradle.lockfile`、`yarn.lock`、`pnpm-lock.yaml`、`bun.lock`、`poetry.lock`、`uv.lock`、`Pipfile.lock`、`pdm.lock`)と、lockfileVersion 1・どこからも要求されていないエントリは `unknown` です。複数のlockfileで判定が異なる場合は `mixed` です
224
225
  - `dependency_groups` はOSV-Scannerが付けた依存グループ(例: `dev`)の生の値です。lockfileの形式によって欠落・不正確なため(pnpmでは付かず、pdmでは `optional` になる等)、参考情報として扱ってください
225
226
  - 修正版の推奨(`suggest_fix`)はJava・JavaScript・Python・Goに対応しています
226
227
 
@@ -387,7 +388,7 @@ npm・Go・PyPIの「同じ系統」は、npmの `^`(キャレット)が互換
387
388
  - プレリリース版(`5.0.0-beta.3`、`15.6.0-canary.61`、Goの疑似バージョン、PyPIの `rc`・`dev` 版。post版は正式版扱い)は、正式版の候補では全CVEを解消できない場合だけ推奨し、`recommended_is_prerelease: true` を付けます。同じTierのプレリリースより、上のTierの正式版を優先します。
388
389
  - 推奨時は `verification: "verified"`、CVEごとの `recommended_status` は `affected` / `not_affected` / `unknown` です。推奨保留時は `not_evaluated` になります。`per_cve_detail.fixed_in` は各CVE単独の候補であり、最終推奨先の判定は `recommended_status` を参照してください。
389
390
  - 現在より新しい修正版候補がないCVEは `tier: "unfixed"` として推奨の修正対象から除外します(情報欠落を含む場合があります)。除外したCVEも推奨先で判定し、その状態を表示します。全CVEがunfixedの場合も `recommended_upgrade` は `null` です。修正版の記載はあるがバージョンとして解釈できないCVE(SemVerでない `13.0` 等)は `tier: "unparseable_fix"` とし、修正版が無いとは扱わず修正対象に残すため、推奨は保留(`no_verified_candidate`)になります。
390
- - npm・Go・PyPIの提案には更新方法の `update_hint` を付けます。PyPIでは、requirements.txtやpyproject.toml・Pipfileの指定を更新してlockfileを再生成し、推移的依存はpipの制約ファイル(`-c`)やuv・Poetryの上書き設定で版を指定します。推移的依存の場合、npmでは要求している直接依存の更新か、ルートの `package.json` の `overrides`(ルートのプロジェクトでのみ有効)で版を指定します。Goでは `go get <module>@<version>` で更新できます。Goのv2以上のメジャーは別のモジュールパス(`/v2` 等)としてOSV上も別パッケージになるため、新しいメジャー系列の修正版は候補に含まれません。現在の版が疑似バージョン(タグのないコミット)の場合は `upgrade_note` に示します。
391
+ - npm・Go・PyPIの提案には更新方法の `update_hint` を付けます。PyPIでは、requirements.txtやpyproject.toml・Pipfileの指定を更新してlockfileを再生成し、推移的依存はpipの制約ファイル(`-c`)やuv・Poetryの上書き設定で版を指定します。Maven(`pom.xml` 由来)では、直接依存は `<dependency>` の版(親POM・プロパティ・BOMで管理していればそちら)、推移的依存は `<dependencyManagement>` での上書きを案内します。推移的依存の場合、npmでは要求している直接依存の更新か、ルートの `package.json` の `overrides`(ルートのプロジェクトでのみ有効)で版を指定します。Goでは `go get <module>@<version>` で更新できます。Goのv2以上のメジャーは別のモジュールパス(`/v2` 等)としてOSV上も別パッケージになるため、新しいメジャー系列の修正版は候補に含まれません。現在の版が疑似バージョン(タグのないコミット)の場合は `upgrade_note` に示します。
391
392
  - **推奨先のOSV照会**: 推奨はスキャンで分かった脆弱性(現在の版に該当するもの)の範囲だけで検証しているため、推奨先に現在の版には該当しない新しい脆弱性がありえます(例: cryptography 3.2 の推奨候補 49.0.0 は、44.0.0 で混入し 50.0.0 で修正された2件に該当)。そこで推奨先を `api.osv.dev` に照会し、該当する脆弱性があれば、それも避けるよう修正版を候補に加えて選び直します(この例では 50.0.0 を推奨し、`upgrade_note` に理由を示します)。結果は `candidate_check` に示します:
392
393
  - `clean`: 推奨先に該当する既知の脆弱性はありません
393
394
  - `has_known_vulnerabilities`: 避けられる修正版の候補が見つからず、推奨先が既知の脆弱性に該当します(`recommended_known_vulnerabilities` にID)
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ if (startupWarning !== null) {
38
38
  // NOTE: リリース時はpackage.jsonのversionと同じ値に更新すること
39
39
  const server = new McpServer({
40
40
  name: "osv-scanner-mcp",
41
- version: "0.8.0",
41
+ version: "0.9.0",
42
42
  });
43
43
  server.registerTool("scan_project", {
44
44
  title: "プロジェクトの依存の脆弱性スキャン(Java / JavaScript / Python / Go)",
@@ -47,7 +47,7 @@ server.registerTool("scan_project", {
47
47
  "Python(poetry.lock / uv.lock / Pipfile.lock / pdm.lock / requirements.txt)、Go(go.mod)。" +
48
48
  "パッケージマネージャーやビルドは実行しない。" +
49
49
  "応答先頭のcoverageを必ず確認すること: lockfileが無いマニフェスト、バージョン未固定のrequirements行、スキャン対象から外したファイルを示す。" +
50
- "各パッケージのdependency_relationは直接依存(direct)か推移的依存(transitive)か(package-lock.json・go.mod・requirements.txtで判定。それ以外はunknown)。" +
50
+ "各パッケージのdependency_relationは直接依存(direct)か推移的依存(transitive)か(package-lock.json・go.mod・requirements.txt・pom.xmlで判定。それ以外はunknown)。" +
51
51
  "coverage.complete=falseの場合は、検出0件でも安全とは判断しないこと。修正版の推奨(suggest_fix)はJava・JavaScript・Python・Goに対応。",
52
52
  inputSchema: {
53
53
  project_path: z
@@ -19,7 +19,9 @@ import { readResponseBytes } from "../utils/readResponseBytes.js";
19
19
  /**
20
20
  * ピン留めするOSV-Scannerのバージョン。更新時は下のチェックサムも必ず更新すること。
21
21
  * 範囲外の読み込みを防ぐ検証はこの版の挙動に合わせているため、更新時は
22
- * docs/DESIGN_TODO.md「B5」の監査と、requirements.txt・親POMの解釈の実機確認をやり直すこと
22
+ * docs/DESIGN_TODO.md「B5」の監査と、requirements.txt・親POMの解釈の実機確認をやり直すこと。
23
+ * pom.xmlの直接/推移的依存の判定(dependencyRelations.tsのpomRelations)も、この版が同じpom.xmlを
24
+ * source.type "lockfile"(宣言された依存)と"unknown"(deps.devで解決された推移的依存)に分けて報告する挙動に依存するため再確認すること
23
25
  */
24
26
  export const PINNED_OSV_SCANNER_VERSION = "2.4.0";
25
27
  /**
@@ -94,7 +94,7 @@ export function extractFixedVersions(vulnDetails, packageName, ecosystem) {
94
94
  return sortVersions(versions, ecosystem);
95
95
  }
96
96
  /**
97
- * パッケージごとのスキャン元ファイル(osv-scannerの`results[].source.path`)。
97
+ * パッケージごとのスキャン元ファイル(osv-scannerの`results[].source.path`と`type`)。
98
98
  * 応答のJSONには出さない(既存の応答を変えない)ため、パッケージのオブジェクトをキーに別に持つ。
99
99
  * 直接/推移的依存の判定(dependencyRelations.ts)が、どのlockfileの依存かを知るために使う
100
100
  */
@@ -116,6 +116,7 @@ export function parseOsvScanOutput(raw) {
116
116
  if (!result)
117
117
  continue;
118
118
  const sourcePath = asString(asRecord(result.source)?.path);
119
+ const sourceType = asString(asRecord(result.source)?.type);
119
120
  if (sourcePath !== null && !sourceFiles.includes(sourcePath)) {
120
121
  sourceFiles.push(sourcePath);
121
122
  }
@@ -133,11 +134,14 @@ export function parseOsvScanOutput(raw) {
133
134
  const key = `${ecosystem}:${name}@${version}`;
134
135
  let entry = packageMap.get(key);
135
136
  if (!entry) {
136
- entry = { name, version, ecosystem, groups: new Set(), sources: new Set(), vulns: new Map() };
137
+ entry = { name, version, ecosystem, groups: new Set(), sources: new Set(), sourceList: [], vulns: new Map() };
137
138
  packageMap.set(key, entry);
138
139
  }
139
- if (sourcePath !== null)
140
- entry.sources.add(sourcePath);
140
+ const sourceKey = JSON.stringify([sourcePath, sourceType]);
141
+ if (sourcePath !== null && !entry.sources.has(sourceKey)) {
142
+ entry.sources.add(sourceKey);
143
+ entry.sourceList.push({ path: sourcePath, type: sourceType });
144
+ }
141
145
  for (const group of asStrings(pkgObj.dependency_groups))
142
146
  entry.groups.add(group);
143
147
  const details = asArray(pkgObj.vulnerabilities);
@@ -196,7 +200,7 @@ function buildReport(sourceFiles, packageMap) {
196
200
  ...(entry.groups.size > 0 ? { dependency_groups: [...entry.groups].sort() } : {}),
197
201
  vulnerabilities,
198
202
  };
199
- packageSourceMap.set(pkg, [...entry.sources]);
203
+ packageSourceMap.set(pkg, entry.sourceList);
200
204
  return pkg;
201
205
  })
202
206
  .sort((a, b) => maxScore(b.vulnerabilities) - maxScore(a.vulnerabilities) || cmpId(a.name, b.name));
@@ -59,6 +59,14 @@ function updateHint(pkg) {
59
59
  }
60
60
  return UPDATE_HINTS.Go;
61
61
  }
62
+ case "Maven":
63
+ if (relation === "direct") {
64
+ return "直接依存です。pom.xmlの<dependency>の版を更新します。版を親POMの<dependencyManagement>・プロパティ・BOMで管理している場合は、そちらを更新します";
65
+ }
66
+ if (relation === "transitive") {
67
+ return "推移的依存です。pom.xmlの<dependencyManagement>で推奨版を指定して上書きする(Mavenの依存の調停で優先されます)か、それを要求している直接依存を更新します";
68
+ }
69
+ return undefined; // gradle.lockfile等: 判定できないため具体的な案内はしない(従来どおり)
62
70
  case "PyPI":
63
71
  if (relation === "direct") {
64
72
  return "直接依存です。requirements.txtの版の指定、またはpyproject.toml・Pipfileの指定を更新し、lockfileを再生成します";
@@ -8,7 +8,7 @@ import path from "node:path";
8
8
  import { ScanToolError } from "../errors.js";
9
9
  import { isRemoteResolutionDisabled, runOsvScan } from "../osv/runner.js";
10
10
  import { packageSources, parseOsvScanOutput } from "../osv/scanReport.js";
11
- import { combineRelations, goModRelations, npmLockRelations, requirementsRelations, } from "../utils/dependencyRelations.js";
11
+ import { combineRelations, goModRelations, npmLockRelations, pomRelations, requirementsRelations, } from "../utils/dependencyRelations.js";
12
12
  import { sanitizeExternalText } from "../utils/externalText.js";
13
13
  import { readRegularFile } from "../utils/safeRead.js";
14
14
  import { detectProject } from "../utils/manifestDetector.js";
@@ -115,6 +115,9 @@ export async function buildRelationLookups(copies, maxTotalBytes = MAX_RELATION_
115
115
  lookup = format === "go.mod" ? goModRelations(text) : npmLockRelations(JSON.parse(text));
116
116
  }
117
117
  }
118
+ else if (format === "pom.xml") {
119
+ lookup = pomRelations(); // ファイルは読まず、osv-scannerのsource.typeで判定する
120
+ }
118
121
  else if (format === "requirements.txt" && entries !== undefined) {
119
122
  lookup = requirementsRelations(entries);
120
123
  }
@@ -129,15 +132,21 @@ export async function buildRelationLookups(copies, maxTotalBytes = MAX_RELATION_
129
132
  /** パッケージに直接/推移的依存の別を付ける。項目は脆弱性の一覧より前に置く */
130
133
  function annotateRelations(packages, lookups) {
131
134
  return packages.map((pkg) => {
132
- const infos = [];
135
+ // 同じファイルが複数の結果に分かれる(pom.xml・requirements.txtの宣言と推移的依存)ため、ファイルごとに1つにまとめる。
136
+ // 同じファイルで直接依存と判定された結果があれば直接依存とする
137
+ const byFile = new Map();
133
138
  const declaredIn = new Set();
139
+ const rank = { direct: 2, transitive: 1, unknown: 0 };
134
140
  for (const source of packageSources(pkg)) {
135
- const entry = lookups.get(source);
136
- const info = entry?.lookup ? entry.lookup(pkg.name, pkg.version) : { relation: "unknown" };
137
- infos.push(info);
141
+ const entry = lookups.get(source.path);
142
+ const info = entry?.lookup ? entry.lookup(pkg.name, pkg.version, source.type) : { relation: "unknown" };
143
+ const previous = byFile.get(source.path);
144
+ if (previous === undefined || rank[info.relation] > rank[previous.relation])
145
+ byFile.set(source.path, info);
138
146
  for (const manifest of info.declaredIn ?? [])
139
147
  declaredIn.add(path.join(entry.lockDir, manifest));
140
148
  }
149
+ const infos = [...byFile.values()];
141
150
  const { relation, introducedBy, replaced } = combineRelations(infos);
142
151
  const { vulnerabilities, ...rest } = pkg;
143
152
  return {
@@ -236,6 +236,15 @@ export function goModRelations(text) {
236
236
  return replaced.has(`${name}@${version}`) || replacedLocal.has(name) ? { relation, replaced: true } : { relation };
237
237
  };
238
238
  }
239
+ /**
240
+ * pom.xmlの依存の関係を、osv-scannerの`source.type`で判定する(pom.xmlは自前で解析しない)。
241
+ * osv-scanner 2.4.0は同じpom.xmlを、宣言された依存(親POM・プロファイル・プロパティ・依存管理をosv-scannerが解釈したもの)を
242
+ * `type: "lockfile"`、deps.devで解決された推移的依存を`type: "unknown"`の結果に分けて報告する(実データで確認)。
243
+ * 文書化された仕様ではないため、想定外のtypeはunknownにする(osv-scannerのピン留め更新時に再確認する)。
244
+ */
245
+ export function pomRelations() {
246
+ return (_name, _version, sourceType) => sourceType === "lockfile" ? { relation: "direct" } : sourceType === "unknown" ? { relation: "transitive" } : UNKNOWN;
247
+ }
239
248
  /** PEP 503の正規化 */
240
249
  function normalizePypi(name) {
241
250
  return name.toLowerCase().replace(/[-_.]+/g, "-");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osv-scanner-mcp",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "MCP server that wraps Google's OSV-Scanner to scan Java projects for known vulnerabilities",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",