osv-scanner-mcp 0.16.0 → 0.18.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
@@ -7,12 +7,13 @@
7
7
 
8
8
  An MCP server that wraps Google's [OSV-Scanner](https://github.com/google/osv-scanner). Ask an MCP client such as Claude to "check this project for vulnerabilities", and it returns the known vulnerabilities (CVE / GHSA) in your dependencies as a report sorted by severity, together with recommended upgrades.
9
9
 
10
- > **Status**: [Published on npm](https://www.npmjs.com/package/osv-scanner-mcp) (`npx -y osv-scanner-mcp`). Supports lockfiles and manifests for Java (Maven / Gradle), JavaScript (npm / yarn / pnpm / bun), Python (Poetry / uv / Pipenv / PDM / requirements.txt), Go, Rust (`Cargo.lock`), and .NET (NuGet: `packages.lock.json`, `obj/project.assets.json`, `packages.config`, `*.deps.json`, and project files), with upgrade recommendations for Maven, npm, PyPI, Go, and crates.io. Setup instructions are provided for Claude Code, Claude Desktop, Codex CLI, Antigravity, and VS Code (GitHub Copilot).
10
+ > **Status**: [Published on npm](https://www.npmjs.com/package/osv-scanner-mcp) (`npx -y osv-scanner-mcp`). Supports lockfiles and manifests for Java (Maven / Gradle), JavaScript (npm / yarn / pnpm / bun), Python (Poetry / uv / Pipenv / PDM / requirements.txt), Go, Rust (`Cargo.lock`), and .NET (NuGet: `packages.lock.json`, `obj/project.assets.json`, `packages.config`, `*.deps.json`, and project files), and PHP (Composer: `composer.lock`), with upgrade recommendations for Maven, npm, PyPI, Go, crates.io, Packagist, and NuGet. Setup instructions are provided for Claude Code, Claude Desktop, Codex CLI, Antigravity, and VS Code (GitHub Copilot).
11
11
 
12
12
  ## Features
13
13
 
14
- - **One-shot multi-language scan**: Pass a project path to `scan_project`, and it detects and scans the Java, JavaScript, Python, Go, Rust, and .NET lockfiles in the project together. Dependencies that could not be scanned (for example, a manifest without a lockfile or an unpinned requirement) are reported up front in `coverage`
15
- - **Upgrade recommendations**: `suggest_fix` recommends, for each vulnerable package, the upgrade closest to the current release line that addresses its fix targets, for Maven, npm, PyPI, Go, and Rust (crates.io). Known unfixed and informational advisories remain visible. The recommended version is also checked against the OSV database for additional fix-target vulnerabilities
14
+ - **PHP / Composer (source checkout)**: `scan_project` scans `composer.lock`, including both `packages` and `packages-dev`, as Packagist packages. `suggest_fix` recommends release versions and checks candidates against OSV. Composer and PHP code are never executed. See [Composer support](#composer-support).
15
+ - **One-shot multi-language scan**: Pass a project path to `scan_project`, and it detects and scans the Java, JavaScript, Python, Go, Rust, .NET, and PHP lockfiles in the project together. Dependencies that could not be scanned (for example, a manifest without a lockfile or an unpinned requirement) are reported up front in `coverage`
16
+ - **Upgrade recommendations**: `suggest_fix` recommends, for each vulnerable package, the upgrade closest to the current release line that addresses its fix targets, for Maven, npm, PyPI, Go, Rust (crates.io), PHP (Packagist), and .NET (NuGet). Known unfixed and informational advisories remain visible. The recommended version is also checked against the OSV database for additional fix-target vulnerabilities
16
17
  - **Rust without false positives**: For `Cargo.lock`, only crates from crates.io are scanned. Your own workspace crates, git dependencies, and crates from private registries are not looked up as crates.io crates (OSV-Scanner alone would match them by name, and send their names to the OSV API). RustSec notices about unmaintained or unsound crates are labeled `informational` and counted apart from vulnerabilities
17
18
  - **.NET without guessing versions**: Each project file is matched to its own lockfile or `obj/project.assets.json`. Without one, only its direct `PackageReference` items are scanned, and a `Version="x"` (which means x or higher) is reported as a lower bound. References whose version cannot be known without running MSBuild (properties, conditions, floating versions, `Update` / `Remove`) are listed instead of being looked up: OSV treats an unparsable version as older than every release and would report every vulnerability for it
18
19
  - **Direct and transitive dependencies**: Packages are marked as direct or transitive dependencies (from `package-lock.json`, `go.mod`, `requirements.txt`, `pom.xml`, `Cargo.lock`, and the .NET files), and update hints are tailored accordingly, for example by naming the direct dependency that pulls in a transitive one
@@ -21,9 +22,21 @@ An MCP server that wraps Google's [OSV-Scanner](https://github.com/google/osv-sc
21
22
  - **SBOM scan**: `scan_sbom` checks the dependencies recorded in a CycloneDX/SPDX JSON SBOM. It states explicitly that the SBOM's completeness and its match with the actual artifacts are not verified
22
23
  - **Scan history (opt-in)**: With `OSV_MCP_HISTORY=1`, each scan is recorded locally, and `scan_history` lists past runs and shows what changed since the previous run (new and resolved vulnerabilities, version changes, coverage changes)
23
24
  - **Severity-sorted reports**: Vulnerabilities are grouped by package and sorted by CVSS score, with five severity labels (critical / high / medium / low / unknown) and summary counts
24
- - **Fixed versions**: Each vulnerability includes `fixed_versions`, sorted correctly by Maven version precedence (including non-semver forms such as `2.17.1-RELEASE`), Semantic Versioning precedence for npm, Go, and Rust, and PEP 440 for PyPI
25
+ - **Fixed versions**: Each vulnerability includes `fixed_versions`, sorted correctly by Maven version precedence (including non-semver forms such as `2.17.1-RELEASE`), Semantic Versioning precedence for npm, Go, and Rust, PEP 440 for PyPI, Composer ordering for Packagist, and NuGet ordering for NuGet
25
26
  - **Security first**: No shell execution, a fixed argument allowlist, path normalization and boundary checks, scanning private copies instead of the original files, and timeouts and output size limits are built in
26
27
 
28
+ ## Composer support
29
+
30
+ Pass a project directory, `composer.lock`, or `composer.json` to `scan_project` or `suggest_fix`. The lockfile records both direct and transitive dependencies; their relationship is currently reported as `unknown`. A `composer.json` without a lockfile in the same directory is reported as missing/unconfirmed in `coverage.lockfile_missing` (a project with no scannable files returns `no_manifest_found`). An ancestor's lockfile does not establish coverage for an independent child project. `vendor` directories are not traversed.
31
+
32
+ The server reads each lockfile safely and scans a private sanitized copy containing only package names and release versions. Repository URLs, dist/source commit references, scripts, and credentials are not passed to OSV-Scanner. Both production and development packages are included. JSON is limited to 16 MiB per lockfile and 64 MiB per scan, with structural work limits. Invalid JSON is excluded; invalid entries and unsupported versions are omitted and reported in `skipped_files`, making `coverage.complete` false.
33
+
34
+ Composer version comparison supports classical numeric releases with one to four components, an optional `v` prefix, and alpha/beta/RC/stable/patch suffixes (including their short forms). Branch/dev versions, aliases, and other formats such as date-based tags are not evaluated. An advisory with unsupported version boundaries cannot establish a verified fix. Upgrade tiers follow the existing cautious 0.x rules; they do not guarantee compatibility with your constraints or PHP runtime. Update the applicable constraints in `composer.json` and regenerate the lockfile in a trusted environment; check PHP and extension requirements first. The server does not run Composer or change project files.
35
+
36
+ Packages with `dist.type: "path"` are local code: they are excluded before building scanner input, so neither their names nor versions are sent to OSV. They are your own code, so they are counted in `coverage.local_composer_packages` (like `local_crates` and `local_projects`) and do not make `coverage.complete` false; their own dependencies are listed in `composer.lock` and scanned. This applies to both production and development dependencies, even when a local package uses the same name and release version as a public package.
37
+
38
+ **Privacy:** other private Composer package names and versions are also sent to `api.osv.dev`. A lockfile alone cannot establish whether a non-path package comes from public Packagist; same-name private packages and VCS forks can produce false positives. `coverage.composer_warnings` warns when scanned entries lack the standard `notification-url` (`https://packagist.org/downloads/`); this is a heuristic, not proof of origin. `coverage.complete` describes inspected dependency entries, not the lockfile's freshness or its match with `composer.json`, and does not verify the PHP runtime or extensions.
39
+
27
40
  ## Requirements
28
41
 
29
42
  - Node.js >= 20.19 (development: Node.js 26.12.1, as specified in `.node-version`)
@@ -206,7 +219,7 @@ Both api.osv.dev and deps.dev are operated by Google. **In every configuration,
206
219
 
207
220
  ### `scan_project`
208
221
 
209
- Detects the lockfiles and manifests in a project and scans the Java / JavaScript / Python / Go / Rust / .NET dependencies together. Package managers and builds are never run.
222
+ Detects the lockfiles and manifests in a project and scans the Java / JavaScript / Python / Go / Rust / .NET / PHP dependencies together. Package managers and builds are never run.
210
223
 
211
224
  **Input**
212
225
 
@@ -224,6 +237,7 @@ Detects the lockfiles and manifests in a project and scans the Java / JavaScript
224
237
  | Go | `go.mod` |
225
238
  | Rust (crates.io) | `Cargo.lock` (lockfile versions 1 to 4) |
226
239
  | .NET (NuGet) | `packages.lock.json` / `packages.<project>.lock.json`, `obj/project.assets.json`, `packages.config` / `packages.<project>.config`, `*.deps.json`, and `*.csproj` / `*.fsproj` / `*.vbproj` (direct references only, when no lockfile matches) |
240
+ | PHP (Packagist) | `composer.lock` (`packages` and `packages-dev`; see [Composer support](#composer-support)) |
227
241
 
228
242
  `.git`, `node_modules`, `target`, `build`, `.idea`, `.vscode`, `.venv`, `venv`, `site-packages`, `__pycache__`, `.tox`, `vendor`, `.cargo`, `bin` and `obj` in a directory that contains a .NET project file (build output of that project), and symbolic links are not searched. Only the detected files are passed to OSV-Scanner, each with its format given explicitly (`coverage.manifests` in the response is exactly the scan scope). The search limits are the same as for `scan_java_project`.
229
243
 
@@ -296,7 +310,13 @@ Detects the lockfiles and manifests in a project and scans the Java / JavaScript
296
310
  - .NET: `Direct` / `Transitive` in `packages.lock.json`; the dependencies of `project.frameworks` in `project.assets.json`; the dependencies of the project library in `*.deps.json`; and every reference read from a project file are direct. `packages.config` does not say, so it is `unknown`. `introduced_by` and `declared_in` are not available yet
297
311
  - Other formats (`gradle.lockfile`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lock`, `poetry.lock`, `uv.lock`, `Pipfile.lock`, and `pdm.lock`), npm lockfile version 1, and entries not reachable from the project are `unknown`. If lockfiles disagree, the value is `mixed`
298
312
  - `dependency_groups` is the raw dependency group reported by OSV-Scanner (for example `dev`). It is missing or inaccurate for some lockfile formats (absent for pnpm, `optional` for pdm, and so on), so treat it as informational
299
- - Upgrade recommendations (`suggest_fix`) are available for Maven, npm, PyPI, Go, and Rust (crates.io). NuGet packages are returned with `verification: "unsupported_ecosystem"` for now
313
+ - Upgrade recommendations (`suggest_fix`) are available for Maven, npm, PyPI, Go, Rust (crates.io), PHP (Packagist), and .NET (NuGet; source checkout).
314
+
315
+ **NuGet recommendations (source checkout).** `suggest_fix` compares NuGet release versions with one to four numeric components (missing components become zero), including legacy revisions such as `1.2.3.4`. Numeric components must fit a nonnegative 32-bit integer; unsupported formats withhold a recommendation. Prerelease identifiers compare numerically when they fit signed 32-bit integers, and otherwise by ASCII case-insensitive lexical order, matching NuGet VersionRelease; numeric prerelease identifiers with leading zeroes are rejected. Build metadata does not affect ordering. Package IDs match case-insensitively. Recommendations use OSV `ECOSYSTEM` affected ranges and are rechecked against OSV; unsupported or incomplete ranges cannot establish a verified fix. Stable candidates are preferred; prerelease-only fixes are labeled. The existing cautious 0.x upgrade tiers apply.
316
+
317
+ For direct dependencies, update the `PackageReference` (or `packages.config` in older projects); with central management, update `PackageVersion` in `Directory.Packages.props`. For transitive dependencies, update the parent dependency or explicitly reference a compatible fixed version if NuGet resolution allows it. Runtime packs (`Microsoft.NETCore.App.Runtime.*`, `Microsoft.AspNetCore.App.Runtime.*`, `Microsoft.WindowsDesktop.App.Runtime.*`) generally require an SDK/runtime or target-framework update rather than a new explicit package reference. Check target-framework/RID compatibility and all version constraints, regenerate restore/lockfiles, and rescan. These are suggestions, not edits; `dotnet restore` and MSBuild are not run.
318
+
319
+ `PackageReference Version="x"` or an inclusive minimum range is scanned at its lower bound, not at a confirmed restored version. Its recommendation retains `version_is_lower_bound: true`, and the note means raising that bound. Coverage still records missing restore/lockfiles and unresolved references. Neither a verified affected-range check nor `candidate_check: "clean"` proves framework compatibility, availability on a private feed, or absence of unknown vulnerabilities.
300
320
 
301
321
  ### `scan_java_project`
302
322
 
@@ -361,7 +381,7 @@ If an existing parent POM cannot be read (larger than 10 MiB, not a regular file
361
381
  ```
362
382
 
363
383
  - `packages` is sorted by the most severe vulnerability, and each `vulnerabilities` list by severity (unknown last)
364
- - `fixed_versions` lists the fixed versions recorded in OSV, in ascending order: by Maven precedence for Maven, Semantic Versioning precedence for npm, Go, and Rust (values that are not valid SemVer come last), and PEP 440 for PyPI (in the order listed by OSV up to v0.5.0); other ecosystems keep the order listed by OSV (the order is not guaranteed). Several release lines may be mixed (for example a 2.12 backport and the 2.15 line). Pre-releases (`5.0.0-beta.3`) and Go pseudo-versions (`0.0.0-20180925071336-cf3bd585ca2a`) may be included. An empty array means that OSV lists no fixed version. Up to v0.4.1, this was always empty for packages outside Maven
384
+ - `fixed_versions` lists the fixed versions recorded in OSV, in ascending order: by Maven precedence for Maven, Semantic Versioning precedence for npm, Go, and Rust (values that are not valid SemVer come last), PEP 440 for PyPI (in the order listed by OSV up to v0.5.0), Composer ordering for Packagist (since v0.17.0), and NuGet ordering for NuGet (since v0.18.0); other ecosystems keep the order listed by OSV (the order is not guaranteed). Several release lines may be mixed (for example a 2.12 backport and the 2.15 line). Pre-releases (`5.0.0-beta.3`) and Go pseudo-versions (`0.0.0-20180925071336-cf3bd585ca2a`) may be included. An empty array means that OSV lists no fixed version. Up to v0.4.1, this was always empty for packages outside Maven
365
385
  - Vulnerabilities without a severity score are reported as `null` / `"unknown"`
366
386
 
367
387
  ### `scan_java_artifact`
@@ -418,7 +438,7 @@ Invalid JSON returns `invalid_sbom`, an unsupported format `unsupported_sbom_for
418
438
 
419
439
  ### `suggest_fix`
420
440
 
421
- Runs the same detection and scan as `scan_project` and recommends an **upgrade version** for each vulnerable package. Recommendations are available for Java (Maven / Gradle), JavaScript (npm), Python (PyPI), Go, and Rust (crates.io). Instead of simply taking the highest fixed version, it picks the fixed version closest to the current release line, falling back through three tiers:
441
+ Runs the same detection and scan as `scan_project` and recommends an **upgrade version** for each vulnerable package. Recommendations are available for Java (Maven / Gradle), JavaScript (npm), Python (PyPI), Go, Rust (crates.io), PHP (Packagist / Composer), and .NET (NuGet; source checkout). Instead of simply taking the highest fixed version, it picks the fixed version closest to the current release line, falling back through three tiers:
422
442
 
423
443
  | Tier | Meaning |
424
444
  |---|---|
@@ -630,7 +650,8 @@ Design notes and open items are in [docs/DESIGN_TODO.md](docs/DESIGN_TODO.md) (i
630
650
  - [x] `suggest_fix` and direct/transitive dependencies for Rust
631
651
  - [x] Scan history and changes between runs (`scan_history`, opt-in)
632
652
  - [x] `scan_project` for .NET (NuGet), with per-project matching and lower bounds for project files
633
- - [ ] `suggest_fix` for .NET (NuGet)
653
+ - [x] `scan_project` and `suggest_fix` for PHP (Composer `composer.lock`)
654
+ - [x] `suggest_fix` for .NET (NuGet; source checkout)
634
655
  - [ ] Direct and transitive dependencies for `gradle.lockfile` and the YAML/TOML lockfiles
635
656
 
636
657
  ## License
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ if (startupWarning !== null) {
66
66
  }
67
67
  }
68
68
  // NOTE: リリース時はpackage.jsonのversionと同じ値に更新すること
69
- const SERVER_VERSION = "0.16.0";
69
+ const SERVER_VERSION = "0.18.0";
70
70
  const server = new McpServer({
71
71
  name: "osv-scanner-mcp",
72
72
  version: SERVER_VERSION,
@@ -75,10 +75,10 @@ const historyConfig = historyConfigFromEnv();
75
75
  /** スキャンのツールを実行履歴に記録する(OSV_MCP_HISTORYが無効なら何もしない) */
76
76
  const recorded = (tool, input, run) => withHistory(tool, input, run, { config: historyConfig, serverVersion: SERVER_VERSION, allowedRootSet: allowedRootFromEnv() !== undefined });
77
77
  server.registerTool("scan_project", {
78
- title: "Scan project dependencies for vulnerabilities (Java / JavaScript / Python / Go / Rust / .NET)",
78
+ title: "Scan project dependencies for vulnerabilities (Java / JavaScript / Python / Go / Rust / .NET / PHP)",
79
79
  description: "Detects the lockfiles and manifests in a project and scans the dependencies for known vulnerabilities (CVE/GHSA). " +
80
80
  "Supported: Java (pom.xml / gradle.lockfile), JavaScript (package-lock.json / npm-shrinkwrap.json / yarn.lock / pnpm-lock.yaml / bun.lock), " +
81
- "Python (poetry.lock / uv.lock / Pipfile.lock / pdm.lock / requirements.txt), Go (go.mod), Rust (Cargo.lock), " +
81
+ "Python (poetry.lock / uv.lock / Pipfile.lock / pdm.lock / requirements.txt), Go (go.mod), Rust (Cargo.lock), PHP (composer.lock, including packages-dev), " +
82
82
  ".NET (packages.lock.json / obj/project.assets.json / packages.config / *.deps.json, and the direct PackageReference items of .csproj / .fsproj / .vbproj files without them). " +
83
83
  "Package managers and builds are never run. " +
84
84
  "Always check coverage at the top of the response: it lists manifests without a lockfile, requirements lines without a pinned version, and excluded files. " +
@@ -89,7 +89,8 @@ server.registerTool("scan_project", {
89
89
  "The project's own projects are counted in local_projects. Names of packages from private NuGet feeds are also sent to api.osv.dev; see nuget_warnings. " +
90
90
  "Vulnerabilities marked informational (RustSec notices such as unmaintained or unsound crates) are not counted in vulnerability_count; they are counted in informational_count. " +
91
91
  "Each package's dependency_relation tells whether it is a direct or transitive dependency (determined for package-lock.json, go.mod, requirements.txt, and pom.xml; unknown otherwise). " +
92
- "If coverage.complete is false, do not conclude that the project is safe even with zero findings. Upgrade recommendations (suggest_fix) are available for Java, JavaScript, Python, Go, and Rust (not yet for .NET). Cargo.lock dependency graphs provide direct/transitive relations, introduced_by, and uniquely mapped declaring Cargo.toml paths in declared_in.",
92
+ "Composer files are scanned from sanitized copies containing package names and release versions only; local dist.type=path packages are not looked up and are counted in local_composer_packages; branch/dev versions and invalid entries are excluded and reported in skipped_files. Composer and PHP code are never run. Other private Composer package names are sent to api.osv.dev; composer_warnings flags missing standard Packagist notification URLs, which are only a heuristic for origin. " +
93
+ "If coverage.complete is false, do not conclude that the project is safe even with zero findings. Upgrade recommendations (suggest_fix) are available for Java, JavaScript, Python, Go, Rust, PHP, and .NET (NuGet). Cargo.lock dependency graphs provide direct/transitive relations, introduced_by, and uniquely mapped declaring Cargo.toml paths in declared_in.",
93
94
  inputSchema: {
94
95
  project_path: z
95
96
  .string()
@@ -113,11 +114,13 @@ server.registerTool("scan_java_project", {
113
114
  server.registerTool("suggest_fix", {
114
115
  title: "Recommend upgrades that fix vulnerabilities",
115
116
  description: "Scans the project with the same detection as scan_project and recommends an upgrade version for each vulnerable package. " +
116
- "Recommendations are available for Java (Maven / Gradle), JavaScript (npm), Python (PyPI), Go, and Rust (crates.io). " +
117
+ "Recommendations are available for Java (Maven / Gradle), JavaScript (npm), Python (PyPI), Go, Rust (crates.io), PHP (Packagist / Composer), and .NET (NuGet). " +
118
+ "NuGet versions support up to four numeric components, case-insensitive prerelease labels, and ignored build metadata. Updates must satisfy target-framework and dependency constraints; runtime packs generally require SDK/runtime or target-framework updates. No dotnet restore or MSBuild is run. " +
119
+ "Composer recommendations support classical numeric release versions with up to four components and alpha/beta/RC/patch suffixes; dev branches, aliases, and unsupported version formats are not evaluated. Check PHP/extension constraints and application compatibility before upgrading. " +
117
120
  "The fixed version closest to the current release line is chosen with a three-tier fallback " +
118
121
  "(same_minor: same release line -> major_internal: same major version -> cross_major: major upgrade), " +
119
122
  "and the response gives the recommended version, the upgrade distance (upgrade_tier), and the fixed version per CVE. For npm, Go, PyPI, and Rust, a minor update within 0.x is also cross_major (may include breaking changes). " +
120
- "Dependencies scanned at a requirements.txt lower bound (>=) are marked version_is_lower_bound, and the recommendation then means raising the lower bound. " +
123
+ "Dependencies scanned at a requirements.txt lower bound (>=) or a NuGet PackageReference minimum/range are marked version_is_lower_bound, and the recommendation then means raising the lower bound rather than confirming the installed version. " +
121
124
  "update_hint explains how to upgrade, depending on whether the package is a direct or transitive dependency (dependency_relation; for npm and Cargo.lock also introduced_by and declared_in). Cargo updates are conditional on parent version requirements; cross-major updates may require Cargo.toml changes. " +
122
125
  "The recommended version is checked against api.osv.dev, and versions with known vulnerabilities that do not affect the current version are avoided (result in candidate_check). " +
123
126
  "clean excludes known unfixed advisories, known informational advisories that are not fix targets, and new Rust unmaintained/notice records; these may still apply. New unsound records are fix targets. " +
@@ -20,6 +20,7 @@ import { candidateStatus } from "./affectedVersions.js";
20
20
  import { versionSchemeFor } from "./versionScheme.js";
21
21
  const TIER_ORDER = ["same_minor", "major_internal", "cross_major"];
22
22
  const UPDATE_HINTS = {
23
+ Packagist: "Update the applicable constraints in composer.json, then regenerate composer.lock in a trusted environment with composer update vendor/package --with-all-dependencies --no-scripts --no-plugins. For a transitive dependency, update the package that requires it or adjust its constraints. Check PHP and extension requirements and application compatibility before updating; Composer is not executed by this server",
23
24
  npm: "For a direct dependency, update the version in package.json. For a transitive dependency, update the direct dependency that requires it, " +
24
25
  "or set the version with overrides in the root package.json (effective only in the root project)",
25
26
  Go: "Update with go get <module>@<version> (a transitive dependency is added to the require block of go.mod and updated as well). " +
@@ -37,6 +38,17 @@ function updateHint(pkg) {
37
38
  const relation = pkg.dependency_relation;
38
39
  const by = pkg.introduced_by?.join(", ");
39
40
  switch (pkg.ecosystem) {
41
+ case "NuGet": {
42
+ if (/^Microsoft\.(?:NETCore|AspNetCore|WindowsDesktop)\.App\.Runtime\./i.test(pkg.name)) {
43
+ return "Runtime pack. Update the .NET SDK/runtime or target framework that supplies this pack and regenerate restore/build outputs; do not add an explicit PackageReference merely to change the runtime pack. Check framework/RID compatibility and rescan. This server does not run dotnet restore or MSBuild";
44
+ }
45
+ const shared = "If centrally managed, change PackageVersion in Directory.Packages.props. Regenerate restore/lockfiles and rescan in a trusted environment; verify target-framework compatibility and dependency constraints. This server does not run dotnet restore or MSBuild";
46
+ if (relation === "direct")
47
+ return "Direct dependency. Update its PackageReference in the project file (or packages.config for older projects). " + shared;
48
+ if (relation === "transitive")
49
+ return "Transitive dependency. Update the direct dependency that requires it, or explicitly reference a compatible fixed version when NuGet dependency resolution permits it. " + shared;
50
+ return "Update the dependency version in the project file or packages.config; for a transitive dependency, update its parent or explicitly reference a compatible fixed version. " + shared;
51
+ }
40
52
  case "npm":
41
53
  if (relation === "direct") {
42
54
  return `Direct dependency. Update the version in ${pkg.declared_in?.join(", ") ?? "package.json"}` +
@@ -132,6 +144,7 @@ function hintFields(pkg, recommended = null, tier = null) {
132
144
  /** スキャン結果に付いた直接/推移的依存の項目を提案にも写す(無ければ何も出さない) */
133
145
  function relationFields(pkg) {
134
146
  return {
147
+ ...(pkg.version_is_lower_bound ? { version_is_lower_bound: true } : {}),
135
148
  ...(pkg.dependency_relation !== undefined ? { dependency_relation: pkg.dependency_relation } : {}),
136
149
  ...(pkg.introduced_by !== undefined ? { introduced_by: pkg.introduced_by } : {}),
137
150
  ...(pkg.introduced_by_omitted !== undefined ? { introduced_by_omitted: pkg.introduced_by_omitted } : {}),
@@ -212,15 +225,19 @@ function buildNote(scheme, pkg, recommended, tier, fixableCount, unfixedCount) {
212
225
  if (unfixedCount > 0)
213
226
  note += `. The remaining ${unfixedCount} CVEs have no fixed version newer than the current one and are excluded from the fix. Check recommended_status`;
214
227
  }
215
- if (pkg.version_is_lower_bound) {
216
- note += `. The current version (${pkg.version}) is the lower bound in requirements.txt (>= or ~=) and may differ from the installed version. ` +
217
- "The recommendation means raising the lower bound to at least that version";
218
- }
228
+ note += lowerBoundNote(pkg);
219
229
  if (pkg.ecosystem === "Go" && GO_PSEUDO_VERSION.test(pkg.version)) {
220
230
  note += ". The current version is a pseudo-version (an untagged commit)";
221
231
  }
222
232
  return note;
223
233
  }
234
+ function lowerBoundNote(pkg) {
235
+ if (!pkg.version_is_lower_bound)
236
+ return "";
237
+ const source = pkg.ecosystem === "NuGet" ? "PackageReference (NuGet minimum/range)" : "requirements.txt (>= or ~=)";
238
+ return `. The current version (${pkg.version}) is the lower bound in ${source} and may differ from the installed version. ` +
239
+ "Any recommendation means raising the lower bound to at least that version";
240
+ }
224
241
  /**
225
242
  * 1パッケージ分のアップグレード提案を組み立てる(同期・純粋)。
226
243
  * contextを渡すと、照会で見つかった脆弱性も避けて候補を選び直す(contextなしの結果が従来の推奨)。
@@ -317,9 +334,9 @@ export function suggestUpgradeForPackage(pkg, context = {}) {
317
334
  upgrade_note: (recommended === null && fixableCount > 0
318
335
  ? "No known fixed version could be confirmed to be outside the affected ranges of every CVE to fix. The recommendation is also withheld when the data are insufficient or use unsupported range types." +
319
336
  (unparseableCount > 0 ? ` ${unparseableCount} CVEs list fixed versions that cannot be parsed as versions (tier: unparseable_fix), so the recommendation is withheld.` : "")
320
- : buildNote(scheme, pkg, recommended, upgradeTier, fixableCount, unfixedCount)) + (informationalNotes ? ` ${informationalNotes}` : ""),
337
+ : buildNote(scheme, pkg, recommended, upgradeTier, fixableCount, unfixedCount)) +
338
+ (recommended === null && fixableCount > 0 ? lowerBoundNote(pkg) : "") + (informationalNotes ? ` ${informationalNotes}` : ""),
321
339
  ...hintFields(pkg, recommended, upgradeTier),
322
- ...(pkg.version_is_lower_bound ? { version_is_lower_bound: true } : {}),
323
340
  per_cve_detail: details,
324
341
  verification: recommended === null ? "no_verified_candidate" : "verified",
325
342
  };
@@ -12,6 +12,8 @@
12
12
  import { compareMavenVersions, mavenVersionSeries } from "../utils/mavenVersion.js";
13
13
  import { compareParsedPep440, parsePep440 } from "../utils/pep440Version.js";
14
14
  import { compareParsedSemver, parseSemver } from "../utils/semverVersion.js";
15
+ import { compareComposerVersions, parseComposerVersion } from "../utils/composerVersion.js";
16
+ import { compareNugetVersions, parseNugetVersion } from "../utils/nugetVersion.js";
15
17
  const SEMVER_ECOSYSTEMS = new Set(["npm", "Go", "crates.io"]);
16
18
  const MAVEN_SCHEME = {
17
19
  isValid: () => true,
@@ -103,8 +105,45 @@ const PEP440_SCHEME = {
103
105
  return major === 0n && minor === 0n ? `${epoch}0.0.${patch}` : `${epoch}${major}.${minor}`;
104
106
  },
105
107
  };
108
+ /** Composer: classical release ordering, with conservative 0.x upgrade tiers. */
109
+ const COMPOSER_SCHEME = {
110
+ isValid: (v) => parseComposerVersion(v) !== null,
111
+ compare: compareComposerVersions,
112
+ isPrerelease: (v) => (parseComposerVersion(v)?.stability ?? 3) < 3,
113
+ classify(current, candidate) {
114
+ const a = parseComposerVersion(current), b = parseComposerVersion(candidate);
115
+ if (!a || !b)
116
+ return "cross_major";
117
+ return classifyCaret({ major: a.parts[0], minor: a.parts[1], patch: a.parts[2] }, { major: b.parts[0], minor: b.parts[1], patch: b.parts[2] });
118
+ },
119
+ seriesLabel(v) {
120
+ const parsed = parseComposerVersion(v);
121
+ return parsed ? `${parsed.parts[0]}.${parsed.parts[1]}` : null;
122
+ },
123
+ };
124
+ /** NuGet VersionRelease ordering; prerelease names and package IDs ignore ASCII case. */
125
+ const NUGET_SCHEME = {
126
+ isValid: (v) => parseNugetVersion(v) !== null,
127
+ compare: compareNugetVersions,
128
+ isPrerelease: (v) => (parseNugetVersion(v)?.prerelease.length ?? 0) > 0,
129
+ classify(current, candidate) {
130
+ const a = parseNugetVersion(current), b = parseNugetVersion(candidate);
131
+ if (!a || !b)
132
+ return "cross_major";
133
+ return classifyCaret({ major: a.parts[0], minor: a.parts[1], patch: a.parts[2] }, { major: b.parts[0], minor: b.parts[1], patch: b.parts[2] });
134
+ },
135
+ seriesLabel(v) {
136
+ const parsed = parseNugetVersion(v);
137
+ return parsed ? parsed.parts[0] === 0 && parsed.parts[1] === 0
138
+ ? `0.0.${parsed.parts[2]}` : `${parsed.parts[0]}.${parsed.parts[1]}` : null;
139
+ },
140
+ };
106
141
  /** 修正版の推奨に対応するエコシステムの操作。未対応ならnull */
107
142
  export function versionSchemeFor(ecosystem) {
143
+ if (ecosystem === "NuGet")
144
+ return NUGET_SCHEME;
145
+ if (ecosystem === "Packagist")
146
+ return COMPOSER_SCHEME;
108
147
  if (ecosystem === "Maven")
109
148
  return MAVEN_SCHEME;
110
149
  if (SEMVER_ECOSYSTEMS.has(ecosystem))
@@ -118,6 +157,8 @@ export function versionSchemeFor(ecosystem) {
118
157
  * PyPIはPEP 503の正規化(小文字化、`-` `_` `.`の連続を`-`)で比較する(実データでは完全一致したが防御的に)
119
158
  */
120
159
  export function samePackageName(a, b, ecosystem) {
160
+ if (ecosystem === "NuGet")
161
+ return a.toLowerCase() === b.toLowerCase();
121
162
  if (ecosystem !== "PyPI")
122
163
  return a === b;
123
164
  const normalize = (name) => name.toLowerCase().replace(/[-_.]+/g, "-");
@@ -107,6 +107,9 @@ export function buildCoverage(project) {
107
107
  }
108
108
  : {}),
109
109
  skipped_files: skipped.items.map((s) => ({ path: sanitizeExternalText(s.path), reason: sanitizeExternalText(s.reason) })),
110
+ // Composer: 自分のコード(dist.type=path)は数だけ示し、completeには影響させない(local_crates・local_projectsと同じ)
111
+ ...(project.manifests.some((m) => m.format === "composer.lock") ? { local_composer_packages: project.composerLocalPackages ?? 0 } : {}),
112
+ ...((project.composerWarnings?.length ?? 0) > 0 ? { composer_warnings: project.composerWarnings.slice(0, MAX_COVERAGE_ITEMS).map(sanitizeExternalText) } : {}),
110
113
  ...(omitted > 0 ? { omitted_items: omitted } : {}),
111
114
  };
112
115
  }
@@ -318,6 +321,11 @@ export async function scanFromSnapshot(project, options) {
318
321
  targets.push({ path: generated, format: "packages.lock.json" });
319
322
  relationSources.push({ copy: generated, format: "packages.lock.json", originalRelative: copy.path, nugetPackages: copy.packages });
320
323
  }
324
+ for (const copy of project.composerCopies ?? []) {
325
+ const generated = await snapshot.writeGenerated(copy.content);
326
+ targets.push({ path: generated, format: "composer.lock" });
327
+ relationSources.push({ copy: generated, format: "composer.lock", originalRelative: copy.path });
328
+ }
321
329
  if (targets.length === 0)
322
330
  return parseOsvScanOutput({ results: [] });
323
331
  const report = await withScopeNotes(project.skippedFiles, () => snapshot.guard(() => runOsvScan(targets, options)));
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `suggest_fix`ツールのハンドラ。
3
3
  * scan_projectと同じ検出・スキャンを実行し、脆弱なパッケージごとの
4
- * 推奨アップグレードバージョン(3段階Tier)を返す。推奨はMaven・npm・Go・PyPI・crates.ioに対応し、
4
+ * 推奨アップグレードバージョン(3段階Tier)を返す。推奨はMaven・npm・Go・PyPI・crates.io・Packagist・NuGetに対応し、
5
5
  * それ以外はunsupported_ecosystemとして返す。
6
6
  */
7
7
  import { isRemoteResolutionDisabled } from "../osv/runner.js";
@@ -0,0 +1,40 @@
1
+ import { parseNpmLock } from "./npmLockCopy.js";
2
+ import { parseComposerVersion } from "./composerVersion.js";
3
+ const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ /** Only names and release versions reach OSV; repository URLs, scripts, and commit IDs are never forwarded. */
5
+ export function composerLockCopy(text, budget) {
6
+ const data = parseNpmLock(text, budget);
7
+ if (!record(data) || !Array.isArray(data.packages) || (data["packages-dev"] !== undefined && !Array.isArray(data["packages-dev"]))) {
8
+ throw new Error("composer.lock must contain a packages array and an optional packages-dev array");
9
+ }
10
+ const issues = [];
11
+ const warnings = [];
12
+ let uncertainOrigin = 0;
13
+ // dist.type=pathは自分のコード(monorepoのパッケージ)。Rustのlocal_crates・.NETのlocal_projectsと同じく数だけ返し、範囲の欠けにしない
14
+ let localPackages = 0;
15
+ const output = {};
16
+ for (const group of ["packages", "packages-dev"]) {
17
+ const packages = [];
18
+ let skipped = 0;
19
+ for (const raw of (data[group] ?? [])) {
20
+ if (record(raw) && record(raw.dist) && raw.dist.type === "path") {
21
+ localPackages++;
22
+ continue;
23
+ }
24
+ if (!record(raw) || typeof raw.name !== "string" || !/^[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/.test(raw.name) || raw.name.length > 200 ||
25
+ typeof raw.version !== "string" || parseComposerVersion(raw.version) === null) {
26
+ skipped++;
27
+ continue;
28
+ }
29
+ packages.push({ name: raw.name, version: raw.version });
30
+ if (raw["notification-url"] !== "https://packagist.org/downloads/")
31
+ uncertainOrigin++;
32
+ }
33
+ output[group] = packages;
34
+ if (skipped > 0)
35
+ issues.push(`${skipped} entries in ${group} were not scanned: invalid package names or non-release versions (branches, aliases, and dev versions are not evaluated)`);
36
+ }
37
+ if (uncertainOrigin > 0)
38
+ warnings.push(`${uncertainOrigin} Composer package entries lack the standard Packagist notification URL. Their names and versions are sent to api.osv.dev as Packagist packages; private or forked packages may produce false positives. The notification URL is only a heuristic and does not prove origin`);
39
+ return { content: JSON.stringify(output), issues, warnings, packageCount: output.packages.length + output["packages-dev"].length, localPackages };
40
+ }
@@ -0,0 +1,36 @@
1
+ const STABILITY = { alpha: 0, a: 0, beta: 1, b: 1, rc: 2, stable: 3, patch: 4, pl: 4, p: 4 };
2
+ export function parseComposerVersion(version) {
3
+ if (version.length > 256)
4
+ return null;
5
+ const match = /^v?(\d{1,5}(?:\.\d+){0,3})(?:[._-]?(alpha|a|beta|b|RC|stable|patch|pl|p)((?:[.-]?\d+(?:[.-]\d+)*)?))?(?:\+[0-9A-Za-z.-]+)?$/i.exec(version);
6
+ if (!match)
7
+ return null;
8
+ const parts = match[1].split(".").map(BigInt);
9
+ while (parts.length < 4)
10
+ parts.push(0n);
11
+ return {
12
+ parts,
13
+ stability: match[2] === undefined ? 3 : STABILITY[match[2].toLowerCase()],
14
+ suffix: match[2]?.toLowerCase() === "stable" ? [] : (match[3] ?? "").replace(/^[.-]/, "").split(/[.-]/).filter(Boolean).map(BigInt),
15
+ };
16
+ }
17
+ export function compareComposerVersions(a, b) {
18
+ const x = parseComposerVersion(a), y = parseComposerVersion(b);
19
+ if (x === null || y === null)
20
+ return 0;
21
+ for (let i = 0; i < 4; i++) {
22
+ if (x.parts[i] !== y.parts[i])
23
+ return x.parts[i] < y.parts[i] ? -1 : 1;
24
+ }
25
+ if (x.stability !== y.stability)
26
+ return x.stability < y.stability ? -1 : 1;
27
+ for (let i = 0; i < Math.max(x.suffix.length, y.suffix.length); i++) {
28
+ // PHP version_compare treats even an extra zero component as greater.
29
+ if (i >= x.suffix.length || i >= y.suffix.length)
30
+ return i >= x.suffix.length ? -1 : 1;
31
+ const left = x.suffix[i], right = y.suffix[i];
32
+ if (left !== right)
33
+ return left < right ? -1 : 1;
34
+ }
35
+ return 0;
36
+ }
@@ -19,6 +19,7 @@ import { createNpmLockWorkBudget, MAX_NPM_LOCK_BYTES, MAX_NPM_LOCK_TOTAL_BYTES,
19
19
  import { analyzeRequirementsFile, createRequirementsReadContext, } from "./requirementsFile.js";
20
20
  import { cargoTomlPackageName, cargoWorkspace, workspaceIncludes, createGlobWorkBudget, classifyCargoPackages, createCargoLockParseBudget, parseCargoLock, UNSCANNABLE_CRATE_REASONS, } from "./cargoLock.js";
21
21
  import { analyzeNuget, collectNugetFile, emptyNugetFiles, NUGET_DEPS_JSON, NUGET_LOCKFILE, NUGET_PACKAGES_CONFIG, NUGET_PROJECT_ASSETS, NUGET_PROJECT_FILE, } from "./nugetProject.js";
22
+ import { composerLockCopy } from "./composerLock.js";
22
23
  /** ファイル名→(エコシステム, 解析形式)。npm-shrinkwrap.jsonはpackage-lock.json形式で解析する */
23
24
  const LOCKFILES = new Map([
24
25
  ["pom.xml", { ecosystem: "Maven", format: "pom.xml" }],
@@ -35,6 +36,7 @@ const LOCKFILES = new Map([
35
36
  ["pdm.lock", { ecosystem: "PyPI", format: "pdm.lock" }],
36
37
  ["go.mod", { ecosystem: "Go", format: "go.mod" }],
37
38
  ["Cargo.lock", { ecosystem: "crates.io", format: "Cargo.lock" }],
39
+ ["composer.lock", { ecosystem: "Packagist", format: "composer.lock" }],
38
40
  ]);
39
41
  /** requirements.txt / requirements-dev.txt / dev-requirements.txt 等 */
40
42
  const REQUIREMENTS_NAME = /^(?:[\w.]+[-_])?requirements(?:[-_][\w.]+)?\.txt$/;
@@ -50,9 +52,11 @@ const LOCK_MARKERS = new Map([
50
52
  ["settings.gradle", "gradle"],
51
53
  ["settings.gradle.kts", "gradle"],
52
54
  ["Cargo.toml", "cargo"],
55
+ ["composer.json", "composer"],
53
56
  ]);
54
- const LOCK_GROUP_ECOSYSTEM = { npm: "npm", pypi: "PyPI", gradle: "Maven", cargo: "crates.io" };
57
+ const LOCK_GROUP_ECOSYSTEM = { npm: "npm", pypi: "PyPI", gradle: "Maven", cargo: "crates.io", composer: "Packagist" };
55
58
  const LOCKFILE_HINTS = {
59
+ composer: "No composer.lock. Generate it in a trusted environment with `composer update --no-install --no-scripts --no-plugins`, then scan again. Composer and PHP code are never executed by this server",
56
60
  npm: "No lockfile. Run `npm install --package-lock-only --ignore-scripts` in a trusted environment " +
57
61
  "(for yarn / pnpm / bun, generate the lockfile with that tool), then scan again",
58
62
  pypi: "No lockfile (poetry.lock / uv.lock / Pipfile.lock / pdm.lock) or requirements.txt with pinned versions. " +
@@ -283,6 +287,9 @@ async function finalize(projectDir, manifests, markers, allowedRootReal, nugetFi
283
287
  targets: [],
284
288
  requirementsCopies: [],
285
289
  cargoCopies: [],
290
+ composerCopies: [],
291
+ composerWarnings: [],
292
+ composerLocalPackages: 0,
286
293
  localCrateCount: 0,
287
294
  unscannableCrates: [],
288
295
  lockfileMissing: await findLockfileMissing(projectDir, markers, manifests, cargoCache),
@@ -298,6 +305,8 @@ async function finalize(projectDir, manifests, markers, allowedRootReal, nugetFi
298
305
  hasNuget: Object.values(nugetFiles).some((list) => list.length > 0),
299
306
  };
300
307
  const readContext = createRequirementsReadContext();
308
+ const composerBudget = createNpmLockWorkBudget();
309
+ let composerBytes = MAX_NPM_LOCK_TOTAL_BYTES;
301
310
  // A null value permanently marks a crate name with multiple declaration paths.
302
311
  const cargoManifests = new Map();
303
312
  const cargoMarkers = new Set([
@@ -312,7 +321,27 @@ async function finalize(projectDir, manifests, markers, allowedRootReal, nugetFi
312
321
  }
313
322
  for (const manifest of manifests) {
314
323
  const absolute = path.join(projectDir, manifest.path);
315
- if (manifest.format === "requirements.txt") {
324
+ if (manifest.format === "composer.lock") {
325
+ const read = await readRegularFile(absolute, { maxBytes: Math.min(MAX_NPM_LOCK_BYTES, composerBytes), boundary: projectDir });
326
+ if (!read.ok) {
327
+ result.skippedFiles.push({ path: manifest.path, reason: capitalizeReason(read.message) });
328
+ continue;
329
+ }
330
+ composerBytes -= read.bytes.length;
331
+ try {
332
+ const parsed = composerLockCopy(read.bytes.toString("utf8"), composerBudget);
333
+ if (parsed.packageCount > 0)
334
+ result.composerCopies.push({ path: manifest.path, content: parsed.content });
335
+ result.skippedFiles.push(...parsed.issues.map((reason) => ({ path: manifest.path, reason })));
336
+ result.composerWarnings.push(...parsed.warnings.map((warning) => `${manifest.path}: ${warning}`));
337
+ result.composerLocalPackages += parsed.localPackages;
338
+ }
339
+ catch {
340
+ result.skippedFiles.push({ path: manifest.path, reason: "composer.lock could not be parsed within the JSON size and work limits" });
341
+ continue;
342
+ }
343
+ }
344
+ else if (manifest.format === "requirements.txt") {
316
345
  const analysis = await analyzeRequirementsFile(absolute, projectDir, readContext);
317
346
  if (!analysis.ok) {
318
347
  result.skippedFiles.push({ path: manifest.path, reason: analysis.reason });
@@ -20,6 +20,7 @@ export const MANIFEST_FORMATS = [
20
20
  "go.mod",
21
21
  "Cargo.lock",
22
22
  "packages.lock.json",
23
+ "composer.lock",
23
24
  ];
24
25
  const FORMAT_SET = new Set(MANIFEST_FORMATS);
25
26
  export function isManifestFormat(value) {
@@ -0,0 +1,41 @@
1
+ const INT_MAX = 2_147_483_647;
2
+ export function parseNugetVersion(version) {
3
+ if (version.length > 256)
4
+ return null;
5
+ const match = /^(\d{1,10}(?:\.\d{1,10}){0,3})(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.exec(version);
6
+ if (!match || match[0] !== version)
7
+ return null;
8
+ const parts = match[1].split(".").map(Number);
9
+ if (parts.some(n => n > INT_MAX))
10
+ return null;
11
+ while (parts.length < 4)
12
+ parts.push(0);
13
+ const prerelease = match[2]?.toLowerCase().split(".") ?? [];
14
+ // NuGet rejects purely numeric release labels with leading zeroes.
15
+ if (prerelease.some(label => /^0\d+$/.test(label)))
16
+ return null;
17
+ return { parts, prerelease };
18
+ }
19
+ export function compareNugetVersions(a, b) {
20
+ const x = parseNugetVersion(a), y = parseNugetVersion(b);
21
+ if (!x || !y)
22
+ return 0;
23
+ for (let i = 0; i < 4; i++)
24
+ if (x.parts[i] !== y.parts[i])
25
+ return x.parts[i] < y.parts[i] ? -1 : 1;
26
+ if (x.prerelease.length === 0 || y.prerelease.length === 0) {
27
+ return x.prerelease.length === y.prerelease.length ? 0 : x.prerelease.length === 0 ? 1 : -1;
28
+ }
29
+ for (let i = 0; i < Math.min(x.prerelease.length, y.prerelease.length); i++) {
30
+ const left = x.prerelease[i], right = y.prerelease[i];
31
+ const numeric = (label) => /^-?\d+$/.test(label) && Number(label) >= -2_147_483_648 && Number(label) <= INT_MAX;
32
+ const leftNumeric = numeric(left);
33
+ const rightNumeric = numeric(right);
34
+ if (leftNumeric !== rightNumeric)
35
+ return leftNumeric ? -1 : 1;
36
+ const l = leftNumeric ? Number(left) : left, r = rightNumeric ? Number(right) : right;
37
+ if (l !== r)
38
+ return l < r ? -1 : 1;
39
+ }
40
+ return Math.sign(x.prerelease.length - y.prerelease.length);
41
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "osv-scanner-mcp",
3
- "version": "0.16.0",
4
- "description": "MCP server that wraps Google's OSV-Scanner to scan Java, JavaScript, Python, Go, Rust, and .NET dependencies for known vulnerabilities and recommend fixes",
3
+ "version": "0.18.0",
4
+ "description": "MCP server that wraps Google's OSV-Scanner to scan Java, JavaScript, Python, Go, Rust, .NET, and PHP dependencies for known vulnerabilities and recommend fixes",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
7
7
  "author": "tedorigawa001",
@@ -20,6 +20,8 @@
20
20
  "rust",
21
21
  "nuget",
22
22
  "dotnet",
23
+ "php",
24
+ "composer",
23
25
  "sca"
24
26
  ],
25
27
  "files": [