osv-scanner-mcp 0.10.0 → 0.11.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 +269 -247
- package/dist/index.js +50 -46
- package/dist/osv/binaryDownloader.js +11 -11
- package/dist/osv/binaryManager.js +5 -5
- package/dist/osv/candidateCheck.js +11 -11
- package/dist/osv/osvApi.js +17 -17
- package/dist/osv/runner.js +8 -8
- package/dist/osv/suggestFix.js +37 -37
- package/dist/tools/scanJavaProject.js +8 -8
- package/dist/tools/scanProject.js +8 -8
- package/dist/tools/toolResult.js +1 -1
- package/dist/utils/manifestDetector.js +11 -11
- package/dist/utils/permissionCheck.js +8 -8
- package/dist/utils/projectDetector.js +7 -7
- package/dist/utils/projectWalk.js +11 -11
- package/dist/utils/requirementsFile.js +18 -18
- package/dist/utils/safeRead.js +10 -6
- package/dist/utils/scanSnapshot.js +17 -17
- package/dist/utils/startupConfig.js +5 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,40 +5,42 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](package.json)
|
|
7
7
|
|
|
8
|
-
Google
|
|
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
|
-
>
|
|
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), and Go, with upgrade recommendations for all four. 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
|
-
-
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
14
|
+
- **One-shot multi-language scan**: Pass a project path to `scan_project`, and it detects and scans the Java, JavaScript, Python, and Go 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 fixes all its known vulnerabilities, for Maven, npm, PyPI, and Go. The recommended version is also checked against the OSV database so that it does not introduce other known vulnerabilities
|
|
16
|
+
- **Direct and transitive dependencies**: Packages are marked as direct or transitive dependencies (from `package-lock.json`, `go.mod`, `requirements.txt`, and `pom.xml`), and update hints are tailored accordingly, for example by naming the direct dependency that pulls in a transitive one
|
|
17
|
+
- **Java project scan**: `scan_java_project` scans only the Java (Maven / Gradle) manifests and returns a formatted report
|
|
18
|
+
- **JAR/WAR scan**: `scan_java_artifact` detects known vulnerabilities from the metadata inside JAR/WAR archives, for projects without a lockfile or with only shaded/fat JARs (best-effort identification, stated explicitly in `coverage`)
|
|
19
|
+
- **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
|
|
20
|
+
- **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
|
|
21
|
+
- **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 and Go, and PEP 440 for PyPI
|
|
22
|
+
- **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
|
|
21
23
|
|
|
22
|
-
##
|
|
24
|
+
## Requirements
|
|
23
25
|
|
|
24
26
|
- Node.js >= 20.19
|
|
25
|
-
- [OSV-Scanner](https://google.github.io/osv-scanner/)
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
27
|
+
- The [OSV-Scanner](https://google.github.io/osv-scanner/) binary — **no manual installation needed**. If it is not found, a pinned version is downloaded from the official GitHub Releases and verified against a SHA256 checksum embedded in the package before use (cached in `~/.cache/osv-scanner-mcp/`)
|
|
28
|
+
- A manually installed binary (on `PATH` or set with `OSV_SCANNER_PATH`) is used first
|
|
29
|
+
- Set `OSV_MCP_AUTO_DOWNLOAD=0` to disable the automatic download
|
|
30
|
+
- Set `OSV_MCP_PREFER_DOWNLOAD=1` to always use the verified downloaded binary instead of one on `PATH` (recommended for production)
|
|
31
|
+
- Scans, `suggest_fix`, and `explain_vulnerability` access the network. Vulnerabilities are looked up in the OSV database (`api.osv.dev`), and **scanning `pom.xml` or `requirements.txt` also connects to deps.dev (`api.deps.dev`) to resolve transitive dependencies**. See [Network destinations and privacy](#network-destinations-and-privacy) for details and how to turn this off
|
|
30
32
|
|
|
31
|
-
##
|
|
33
|
+
## Setup
|
|
32
34
|
|
|
33
|
-
### Claude Code
|
|
35
|
+
### Claude Code
|
|
34
36
|
|
|
35
37
|
```bash
|
|
36
38
|
claude mcp add osv-scanner -- npx -y osv-scanner-mcp
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
### Claude Desktop
|
|
41
|
+
### Claude Desktop
|
|
40
42
|
|
|
41
|
-
`claude_desktop_config.json
|
|
43
|
+
Add to `claude_desktop_config.json`:
|
|
42
44
|
|
|
43
45
|
```json
|
|
44
46
|
{
|
|
@@ -51,27 +53,27 @@ claude mcp add osv-scanner -- npx -y osv-scanner-mcp
|
|
|
51
53
|
}
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
### Codex CLI
|
|
56
|
+
### Codex CLI
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
59
|
codex mcp add osv-scanner -- npx -y osv-scanner-mcp
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Or add to `~/.codex/config.toml`:
|
|
61
63
|
|
|
62
64
|
```toml
|
|
63
65
|
[mcp_servers.osv-scanner]
|
|
64
66
|
command = "npx"
|
|
65
67
|
args = ["-y", "osv-scanner-mcp"]
|
|
66
|
-
startup_timeout_sec = 60 #
|
|
67
|
-
tool_timeout_sec = 300 #
|
|
68
|
+
startup_timeout_sec = 60 # allow time for the first npx package download
|
|
69
|
+
tool_timeout_sec = 300 # default is 60 seconds; allow for the binary download plus a scan (120 seconds by default)
|
|
68
70
|
```
|
|
69
71
|
|
|
70
|
-
>
|
|
72
|
+
> **Note**: Codex's MCP tool timeout defaults to 60 seconds, while this server's scan timeout defaults to 120 seconds. With the default, Codex may time out first on the first run (which downloads OSV-Scanner) or on larger projects. Raising `tool_timeout_sec` as above is recommended.
|
|
71
73
|
|
|
72
|
-
### Antigravity
|
|
74
|
+
### Antigravity
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
Open **MCP Servers → Manage MCP Servers → View raw config** in the agent panel and add to `mcp_config.json` (same format as Claude Desktop):
|
|
75
77
|
|
|
76
78
|
```json
|
|
77
79
|
{
|
|
@@ -84,13 +86,13 @@ tool_timeout_sec = 300 # 既定60秒。バイナリ自動ダウンロード+
|
|
|
84
86
|
}
|
|
85
87
|
```
|
|
86
88
|
|
|
87
|
-
### VS Code(GitHub Copilot)
|
|
89
|
+
### VS Code (GitHub Copilot)
|
|
88
90
|
|
|
89
91
|
```bash
|
|
90
92
|
code --add-mcp '{"name":"osv-scanner","command":"npx","args":["-y","osv-scanner-mcp"]}'
|
|
91
93
|
```
|
|
92
94
|
|
|
93
|
-
|
|
95
|
+
Or add to the workspace's `.vscode/mcp.json` (also available from **MCP: Add Server** in the Command Palette):
|
|
94
96
|
|
|
95
97
|
```json
|
|
96
98
|
{
|
|
@@ -104,41 +106,41 @@ code --add-mcp '{"name":"osv-scanner","command":"npx","args":["-y","osv-scanner-
|
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
###
|
|
109
|
+
### From source
|
|
108
110
|
|
|
109
111
|
```bash
|
|
110
112
|
git clone https://github.com/tedorigawa001/OSV-Scanner-MCP.git
|
|
111
113
|
cd OSV-Scanner-MCP
|
|
112
114
|
npm install
|
|
113
115
|
npm run build
|
|
114
|
-
#
|
|
116
|
+
# When registering, use `node /path/to/OSV-Scanner-MCP/dist/index.js` instead of `npx -y osv-scanner-mcp`
|
|
115
117
|
```
|
|
116
118
|
|
|
117
|
-
###
|
|
119
|
+
### Environment variables
|
|
118
120
|
|
|
119
|
-
|
|
|
121
|
+
| Variable | Description |
|
|
120
122
|
|---|---|
|
|
121
|
-
| `OSV_SCANNER_PATH` |
|
|
122
|
-
| `OSV_MCP_ALLOWED_ROOT` |
|
|
123
|
-
| `OSV_MCP_REQUIRE_ALLOWED_ROOT` | `1`
|
|
124
|
-
| `OSV_MCP_MAX_CONCURRENT_SCANS` |
|
|
125
|
-
| `OSV_MCP_AUTO_DOWNLOAD` | `0`
|
|
126
|
-
| `OSV_MCP_PREFER_DOWNLOAD` | `1`
|
|
127
|
-
| `OSV_MCP_NO_CANDIDATE_CHECK` | `1`
|
|
128
|
-
| `OSV_MCP_NO_REMOTE_RESOLUTION` | `1`
|
|
129
|
-
|
|
130
|
-
>
|
|
131
|
-
|
|
132
|
-
>
|
|
133
|
-
> - `OSV_MCP_ALLOWED_ROOT
|
|
134
|
-
> - `OSV_MCP_REQUIRE_ALLOWED_ROOT=1` —
|
|
135
|
-
> - `OSV_SCANNER_PATH
|
|
123
|
+
| `OSV_SCANNER_PATH` | Explicit path to the osv-scanner binary. If unset, `PATH` is searched, then the binary is downloaded automatically. **If the path is invalid, the server fails instead of falling back** (to avoid running an unintended binary) |
|
|
124
|
+
| `OSV_MCP_ALLOWED_ROOT` | If set, scans outside this directory are refused (the boundary against path traversal). **Recommended.** An empty or whitespace-only value is treated as unset |
|
|
125
|
+
| `OSV_MCP_REQUIRE_ALLOWED_ROOT` | If `1` or `true`, the server refuses to start when `OSV_MCP_ALLOWED_ROOT` is not set (fail-closed mode for production) |
|
|
126
|
+
| `OSV_MCP_MAX_CONCURRENT_SCANS` | Maximum number of concurrent scans (default `2`, maximum `16`). Requests beyond the limit fail immediately instead of waiting |
|
|
127
|
+
| `OSV_MCP_AUTO_DOWNLOAD` | `0` or `false` disables the automatic binary download (enabled by default) |
|
|
128
|
+
| `OSV_MCP_PREFER_DOWNLOAD` | If `1` or `true`, always use the checksum-verified downloaded binary instead of osv-scanner on `PATH` (protects against a fake binary planted on `PATH`; an explicit `OSV_SCANNER_PATH` still takes precedence) |
|
|
129
|
+
| `OSV_MCP_NO_CANDIDATE_CHECK` | If `1` or `true`, `suggest_fix` does not query OSV about recommended versions (`candidate_check` is `disabled`, and recommended versions are not checked for known vulnerabilities that do not affect the current version) |
|
|
130
|
+
| `OSV_MCP_NO_REMOTE_RESOLUTION` | If `1` or `true`, transitive dependencies of `pom.xml` and `requirements.txt` are not resolved through deps.dev. **This stops only the requests to deps.dev; package names and versions are still sent to `api.osv.dev` for the vulnerability lookup.** Vulnerabilities in transitive dependencies are then not detected, and responses say so in `dependency_resolution.warning`. See [Network destinations and privacy](#network-destinations-and-privacy) |
|
|
131
|
+
|
|
132
|
+
> **Recommendation**: The server works without `OSV_MCP_ALLOWED_ROOT`, but then any absolute path can be scanned. To prevent malicious instructions (prompt injection) from scanning unintended directories, set it to the root of your projects (for example `~/projects`). Pass it in the client configuration, for example `"env": {"OSV_MCP_ALLOWED_ROOT": "/Users/you/projects"}` (in Codex CLI's TOML, use a `[mcp_servers.osv-scanner.env]` section).
|
|
133
|
+
|
|
134
|
+
> **Recommended production configuration**: On shared servers, CI, and other production environments, set all three of the following:
|
|
135
|
+
> - `OSV_MCP_ALLOWED_ROOT=/root/of/scan/targets` — fixes the scan boundary
|
|
136
|
+
> - `OSV_MCP_REQUIRE_ALLOWED_ROOT=1` — refuses to start without a boundary (fail-closed)
|
|
137
|
+
> - `OSV_SCANNER_PATH=/absolute/path/owned/by/an/administrator` or `OSV_MCP_PREFER_DOWNLOAD=1` — fixes which binary is run, independent of `PATH`
|
|
136
138
|
>
|
|
137
|
-
>
|
|
139
|
+
> Check [Network destinations and privacy](#network-destinations-and-privacy) for where dependency information is sent. In every configuration, package names and versions are sent to `api.osv.dev` for the vulnerability lookup.
|
|
138
140
|
|
|
139
|
-
###
|
|
141
|
+
### Running with restricted permissions
|
|
140
142
|
|
|
141
|
-
|
|
143
|
+
As an additional layer of defense, you can limit what the server can read and write with Node's permission model (`--permission`). This is optional and does not change the default way of starting the server. `--permission` is available in Node 22.13, 23.5, and later (Node 20's experimental `--experimental-permission` has not been tested). Since `npx` cannot pass Node flags, start the server with `node` directly:
|
|
142
144
|
|
|
143
145
|
```json
|
|
144
146
|
{
|
|
@@ -165,57 +167,57 @@ npm run build
|
|
|
165
167
|
}
|
|
166
168
|
```
|
|
167
169
|
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
170
|
+
- Read access: the server itself (the directory containing `dist` and `node_modules`), the scan targets (`OSV_MCP_ALLOWED_ROOT`), the temporary directory, and the osv-scanner binary
|
|
171
|
+
- The temporary directory (`os.tmpdir()`, `$TMPDIR` on macOS) needs read access under **both the symlinked and the resolved path** (on macOS, `/var/folders/...` is a symbolic link to `/private/var/folders/...`). Write access is needed on the resolved path
|
|
172
|
+
- If you use the automatic download, also allow reading and writing the cache (`$XDG_CACHE_HOME/osv-scanner-mcp`, by default `~/.cache/osv-scanner-mcp`). Otherwise, set `OSV_SCANNER_PATH`
|
|
173
|
+
- `--allow-child-process` is required to run osv-scanner. **osv-scanner runs as a child process and is not restricted by the permission model** (Node itself warns that this flag weakens the permission model). The impact is limited because osv-scanner only receives verified private copies, but use an OS-level mechanism such as a container if you need stronger isolation
|
|
174
|
+
- If a required permission is missing, the server prints a warning to stderr at startup, and scans return `permission_denied` (naming the missing permission and the path)
|
|
173
175
|
|
|
174
|
-
###
|
|
176
|
+
### Network destinations and privacy
|
|
175
177
|
|
|
176
|
-
osv-scanner v2.4.0
|
|
178
|
+
Destinations confirmed with osv-scanner v2.4.0 (2026-10-07):
|
|
177
179
|
|
|
178
|
-
|
|
|
180
|
+
| Operation | Destinations | Information sent |
|
|
179
181
|
|---|---|---|
|
|
180
|
-
| `pom.xml`
|
|
181
|
-
| `requirements.txt`
|
|
182
|
-
|
|
|
183
|
-
| `scan_java_artifact` / `scan_sbom` | `api.osv.dev` |
|
|
184
|
-
| `suggest_fix`
|
|
185
|
-
| `explain_vulnerability` | `api.osv.dev` |
|
|
186
|
-
|
|
|
182
|
+
| Scanning `pom.xml` (`scan_project` / `scan_java_project` / `suggest_fix`) | `api.osv.dev`, **`api.deps.dev`** | Package names and versions. To resolve transitive dependencies, the names and versions of the dependencies declared in `pom.xml` (including internal packages) are sent to deps.dev |
|
|
183
|
+
| Scanning `requirements.txt` (`scan_project` / `suggest_fix`) | `api.osv.dev`, **`api.deps.dev`** | As with `pom.xml`, the names and versions of the listed dependencies are sent to deps.dev to resolve transitive dependencies. Package indexes given with `--index-url` and similar options are not contacted |
|
|
184
|
+
| Scanning lockfiles (`gradle.lockfile`, npm lockfiles such as `package-lock.json`, Python lockfiles such as `poetry.lock`, `go.mod`) | `api.osv.dev` | Package names and versions (lockfiles already list every dependency, so nothing is resolved remotely) |
|
|
185
|
+
| `scan_java_artifact` / `scan_sbom` | `api.osv.dev` | Names and versions of the identified packages |
|
|
186
|
+
| `suggest_fix` checking recommended versions | `api.osv.dev` | The names of packages with a recommendation (already sent during the scan) and the candidate versions (published fixed versions). Disable with `OSV_MCP_NO_CANDIDATE_CHECK=1` |
|
|
187
|
+
| `explain_vulnerability` | `api.osv.dev` | The vulnerability ID |
|
|
188
|
+
| Automatic binary download (first run only) | GitHub (official Releases) | Nothing (downloads the pinned binary) |
|
|
187
189
|
|
|
188
|
-
api.osv.dev
|
|
190
|
+
Both api.osv.dev and deps.dev are operated by Google. **In every configuration, the names and versions of scanned packages are sent to `api.osv.dev` for the vulnerability lookup** (offline lookup is not supported).
|
|
189
191
|
|
|
190
|
-
- **deps.dev
|
|
191
|
-
-
|
|
192
|
+
- **To stop sending data to deps.dev**: Set `OSV_MCP_NO_REMOTE_RESOLUTION=1`. Transitive dependencies of `pom.xml` and `requirements.txt` are then not resolved, and only `api.osv.dev` is contacted. This stops only the requests to deps.dev; requests to OSV continue. Only the dependencies written in those files are scanned, so **vulnerabilities in transitive dependencies are missed**. Responses of `scan_project` / `scan_java_project` / `suggest_fix` show this as `transitive_resolution: "disabled"` in `dependency_resolution` with a warning, so it can be told apart from zero findings. To scan transitive dependencies without deps.dev, use lockfiles (`gradle.lockfile`, `poetry.lock`, and so on)
|
|
193
|
+
- **Arbitrary repositories are never contacted**: osv-scanner's `--data-source native` mode connects to any URL listed in `<repositories>` of the scanned `pom.xml` (a malicious `pom.xml` could make it contact an attacker's server). This server never uses that mode and sets `deps.dev` explicitly
|
|
192
194
|
|
|
193
|
-
##
|
|
195
|
+
## Tools
|
|
194
196
|
|
|
195
197
|
### `scan_project`
|
|
196
198
|
|
|
197
|
-
|
|
199
|
+
Detects the lockfiles and manifests in a project and scans the Java / JavaScript / Python / Go dependencies together. Package managers and builds are never run.
|
|
198
200
|
|
|
199
|
-
|
|
201
|
+
**Input**
|
|
200
202
|
|
|
201
|
-
|
|
|
203
|
+
| Parameter | Type | Description |
|
|
202
204
|
|---|---|---|
|
|
203
|
-
| `project_path` | string |
|
|
205
|
+
| `project_path` | string | Absolute path to the project directory, or to a supported lockfile or manifest (a file given directly is the only file scanned) |
|
|
204
206
|
|
|
205
|
-
|
|
207
|
+
**Supported files**
|
|
206
208
|
|
|
207
|
-
|
|
|
209
|
+
| Ecosystem | Files |
|
|
208
210
|
|---|---|
|
|
209
|
-
| Java(Maven) | `pom.xml
|
|
210
|
-
| JavaScript(npm) | `package-lock.json
|
|
211
|
-
| Python(PyPI) | `poetry.lock
|
|
211
|
+
| Java (Maven) | `pom.xml`, `gradle.lockfile`, `buildscript-gradle.lockfile` |
|
|
212
|
+
| JavaScript (npm) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lock` (text format) |
|
|
213
|
+
| Python (PyPI) | `poetry.lock`, `uv.lock`, `Pipfile.lock`, `pdm.lock`, `requirements.txt` (and variants such as `requirements-dev.txt`) |
|
|
212
214
|
| Go | `go.mod` |
|
|
213
215
|
|
|
214
|
-
`.git
|
|
216
|
+
`.git`, `node_modules`, `target`, `build`, `.idea`, `.vscode`, `.venv`, `venv`, `site-packages`, `__pycache__`, `.tox`, `vendor`, 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`.
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
**`requirements.txt` files are not passed to OSV-Scanner as they are.** The server parses them, rewrites only the dependency lines it can interpret into simple forms such as `name==version`, and scans a private temporary copy (deleted after the scan). OSV-Scanner interprets include directives on its own (it even treats `- r ../x.txt`, with a space, as an include and reads files outside the scan scope), so the copy never contains include directives or options. Includes (`-r` / `--requirement`) are expanded by the server, and only when the included file is inside the project directory.
|
|
217
219
|
|
|
218
|
-
|
|
220
|
+
**Reading the output**
|
|
219
221
|
|
|
220
222
|
```json
|
|
221
223
|
{
|
|
@@ -223,13 +225,13 @@ api.osv.dev と deps.dev はどちらも Google が運営するサービスで
|
|
|
223
225
|
"dependency_resolution": { "transitive_resolution": "enabled" },
|
|
224
226
|
"coverage": {
|
|
225
227
|
"complete": false,
|
|
226
|
-
"warning": "
|
|
228
|
+
"warning": "…",
|
|
227
229
|
"manifests": [{ "path": "web/package-lock.json", "ecosystem": "npm", "format": "package-lock.json" }],
|
|
228
|
-
"lockfile_missing": [{ "path": "svc/package.json", "ecosystem": "npm", "status": "missing", "hint": "
|
|
230
|
+
"lockfile_missing": [{ "path": "svc/package.json", "ecosystem": "npm", "status": "missing", "hint": "…" }],
|
|
229
231
|
"unpinned_requirements": [{ "file": "py/requirements.txt", "line": 2, "name": "Jinja2", "specifier": ">=2.0", "kind": "lower_bound" }],
|
|
230
232
|
"unscannable_requirements": [
|
|
231
|
-
{ "file": "py/requirements.txt", "line": 4, "text": "-e git+https://…", "reason": "
|
|
232
|
-
{ "file": "py/requirements.txt", "line": 5, "text": "-r ../shared/base.txt", "reason": "
|
|
233
|
+
{ "file": "py/requirements.txt", "line": 4, "text": "-e git+https://…", "reason": "…" },
|
|
234
|
+
{ "file": "py/requirements.txt", "line": 5, "text": "-r ../shared/base.txt", "reason": "…" }
|
|
233
235
|
],
|
|
234
236
|
"skipped_files": []
|
|
235
237
|
},
|
|
@@ -244,53 +246,53 @@ api.osv.dev と deps.dev はどちらも Google が運営するサービスで
|
|
|
244
246
|
}
|
|
245
247
|
```
|
|
246
248
|
|
|
247
|
-
-
|
|
248
|
-
- `lockfile_missing`: lockfile
|
|
249
|
-
- `unpinned_requirements`: requirements.txt
|
|
250
|
-
- `unscannable_requirements`:
|
|
251
|
-
- `skipped_files`:
|
|
252
|
-
-
|
|
253
|
-
- `ecosystem_breakdown`
|
|
254
|
-
- `dependency_relation`
|
|
255
|
-
- `package-lock.json`(v2
|
|
256
|
-
- `go.mod`: `// indirect`
|
|
257
|
-
- `requirements.txt`:
|
|
258
|
-
- `pom.xml`: OSV-Scanner
|
|
259
|
-
-
|
|
260
|
-
- `dependency_groups`
|
|
261
|
-
-
|
|
249
|
+
- **Always check `coverage`.** If `complete` is `false`, some dependencies were not scanned, so zero findings does not mean the project is safe
|
|
250
|
+
- `lockfile_missing`: manifests without a lockfile (`package.json`, `pyproject.toml`, `Pipfile`, `setup.py`, `build.gradle`, and so on). Not recorded if a lockfile of the same ecosystem is in the same directory. If there is only a lockfile in a parent directory, the manifest is not recorded only when `package-lock.json` (v2 or later) is confirmed to contain that directory (npm workspaces). Otherwise it is recorded with `status: "missing"`, or `status: "unconfirmed"` for formats that cannot be checked (yarn.lock, Python lockfiles, and so on). Generate the lockfile as described in `hint` (in a trusted environment) and scan again
|
|
251
|
+
- `unpinned_requirements`: lines in `requirements.txt` that do not pin a version. `kind` is `unpinned` (no version), `range` (`>`, `<`, `!=`, `==1.*`, combined ranges, and so on), or `lower_bound` (`>=`, `~=`). OSV-Scanner does not scan `unpinned` and `range` lines, and scans `lower_bound` lines at the lower bound (those packages are marked `version_is_lower_bound: true` and may differ from the installed version)
|
|
252
|
+
- `unscannable_requirements`: lines that are not scanned, with the reason: `-e`, `name @ URL`, paths, includes that were not expanded (outside the project directory, missing, URLs, or over the limits), constraint files (`-c`, not applied), and options or version specifiers that cannot be interpreted. Lines that cannot be interpreted are recorded here instead of being ignored
|
|
253
|
+
- `skipped_files`: files excluded from the scan, with the reason (a `requirements.txt` that cannot be read or is larger than 1 MiB, or a `pom.xml` whose parent POM chain references a file outside the allowed root). A `pom.xml` scanned without its parent POM, because the parent could not be read, is also listed here with the reason (see [Parent POMs](#parent-poms))
|
|
254
|
+
- Each list holds up to 200 entries; the number of omitted entries is returned in `omitted_items`
|
|
255
|
+
- `ecosystem_breakdown` includes ecosystems with zero findings, to show that they were scanned
|
|
256
|
+
- `dependency_relation` shows whether a package is a direct (`direct`) or transitive (`transitive`) dependency. OSV-Scanner does not report this, so the server determines it by parsing the scanned copies:
|
|
257
|
+
- `package-lock.json` (v2 or later): dependencies of the root and workspace `package.json` files, resolved with Node's lookup rules (nested `node_modules` first, then parent directories), are direct; everything reachable from them is transitive. Direct dependencies list the `package.json` files that declare them in `declared_in`; transitive dependencies list the direct dependencies that require them in `introduced_by` (up to 10, with the rest counted in `introduced_by_omitted`). A version that is both a direct dependency and required by another dependency is `direct` and also has `introduced_by`
|
|
258
|
+
- `go.mod`: `require` lines without `// indirect` are direct. Modules affected by a `replace` directive are marked `replaced_in_go_mod: true` (OSV-Scanner reports the replacement module and version)
|
|
259
|
+
- `requirements.txt`: dependencies written in the file are direct; dependencies resolved through deps.dev are transitive
|
|
260
|
+
- `pom.xml`: OSV-Scanner reports the dependencies declared in `pom.xml` (and its parent POMs) and the transitive dependencies resolved through deps.dev as separate results (`source.type` `lockfile` / `unknown`), and this split is used (so parent POMs, profiles, properties, and dependency management are interpreted exactly as OSV-Scanner does). `introduced_by` / `declared_in` are not available. This relies on undocumented OSV-Scanner output, so anything unexpected is reported as `unknown`
|
|
261
|
+
- Other formats (`gradle.lockfile`, `yarn.lock`, `pnpm-lock.yaml`, `bun.lock`, `poetry.lock`, `uv.lock`, `Pipfile.lock`, `pdm.lock`), lockfile version 1, and entries not reachable from the project are `unknown`. If lockfiles disagree, the value is `mixed`
|
|
262
|
+
- `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
|
|
263
|
+
- Upgrade recommendations (`suggest_fix`) are available for Java, JavaScript, Python, and Go
|
|
262
264
|
|
|
263
265
|
### `scan_java_project`
|
|
264
266
|
|
|
265
|
-
Java(Maven)
|
|
267
|
+
Scans a Java (Maven) project and returns a known-vulnerability report.
|
|
266
268
|
|
|
267
|
-
|
|
269
|
+
**Input**
|
|
268
270
|
|
|
269
|
-
|
|
|
271
|
+
| Parameter | Type | Description |
|
|
270
272
|
|---|---|---|
|
|
271
|
-
| `project_path` | string |
|
|
273
|
+
| `project_path` | string | Absolute path to the project directory, or to a `pom.xml` / `gradle.lockfile` |
|
|
272
274
|
|
|
273
|
-
> **Gradle
|
|
275
|
+
> **Gradle projects**: Only the **lockfile approach** is supported (running the build would execute arbitrary code in `build.gradle`, so it is not used for security reasons). If there is no `gradle.lockfile`, generate one with `./gradlew dependencies --write-locks` (if dependency locking is not configured, add `dependencyLocking { lockAllConfigurations() }` to `build.gradle`).
|
|
274
276
|
|
|
275
|
-
>
|
|
277
|
+
> **Scan scope**: For a directory, every `pom.xml` / `gradle.lockfile` / `buildscript-gradle.lockfile` below it is detected regardless of depth, and **only the detected files** are scanned (`manifests` in the response is exactly the scan scope). Non-Java files in the same directories, such as `package-lock.json` and `requirements.txt`, are not scanned. `.git`, `node_modules`, `target`, `build`, `.idea`, `.vscode`, and symbolic links are not searched. If the search visits more than 200,000 entries or finds more than 1,000 manifests, `manifest_search_limit_exceeded` is returned instead of silently truncating the results. When a manifest such as `pom.xml` is given directly, the directory is not searched and **only that file** is scanned (also useful as a workaround when the limits are reached).
|
|
276
278
|
|
|
277
|
-
####
|
|
279
|
+
#### Parent POMs
|
|
278
280
|
|
|
279
|
-
OSV-Scanner
|
|
281
|
+
OSV-Scanner reads the parent POM referenced by `<parent>` in `pom.xml` (the file at `<relativePath>`, or `../pom.xml` by default as in Maven), follows the chain to its own parents, and includes the dependencies declared there. This is why scanning a submodule alone still detects the dependencies it inherits.
|
|
280
282
|
|
|
281
|
-
`OSV_MCP_ALLOWED_ROOT`
|
|
283
|
+
When `OSV_MCP_ALLOWED_ROOT` is set, a `pom.xml` whose parent POM chain references a file **outside the allowed root** at any point is excluded from the scan (so that the contents of files outside the allowed root never reach the results or the lookup services). Excluded files are listed with the reason in `skipped_manifests` (`coverage.skipped_files` in `scan_project`), and `scope_warning` says that zero findings should not be taken as safe. If every manifest is excluded, or such a `pom.xml` is given directly, `path_outside_allowed_root` is returned.
|
|
282
284
|
|
|
283
|
-
|
|
285
|
+
If an existing parent POM cannot be read (larger than 10 MiB, not a regular file such as a named pipe, a symbolic link at the final path component, and so on), the `pom.xml` is scanned without it, and the possibly missing inherited dependencies are reported with the reason in `incomplete_manifests` (`coverage.skipped_files` in `scan_project`, with `coverage.complete` set to `false`). If the child `pom.xml` itself declares no dependencies and the scan ends with `no_packages_found`, the error message includes the same reason. A parent POM that does not exist (for example the default `../pom.xml` of a root `pom.xml`) is not reported, because OSV-Scanner would not read it from the original location either.
|
|
284
286
|
|
|
285
|
-
-
|
|
286
|
-
- `<relativePath/>`(
|
|
287
|
-
-
|
|
288
|
-
- XML
|
|
289
|
-
-
|
|
290
|
-
-
|
|
291
|
-
- `OSV_MCP_ALLOWED_ROOT`
|
|
287
|
+
- Parent POMs inside the allowed root are still read (scanning submodules inside the allowed root keeps working)
|
|
288
|
+
- `<relativePath/>` (empty) does not reference a local parent POM and is ignored
|
|
289
|
+
- The parent reference is read the way OSV-Scanner's (Go) XML decoder reads it: elements are matched by local name regardless of namespace prefix (`<m:parent>` counts as a parent), only a `parent` directly under the root element is used, and character references are expanded
|
|
290
|
+
- As the XML specification requires, line endings (CRLF and CR) are normalized to LF before parsing
|
|
291
|
+
- A `pom.xml` is excluded when the same interpretation cannot be guaranteed: multiple `parent` or `relativePath` elements directly under the root, CDATA, a DOCTYPE, unknown entity references, property references (`${...}`), unclosed tags, content that is not valid UTF-8, or a `relativePath` containing control characters (newlines, tabs, and so on), whitespace other than a regular space, or format characters (regular spaces and non-ASCII directory names are allowed)
|
|
292
|
+
- OSV-Scanner does not read a parent whose GAV does not match, but this server does not check the GAV and excludes any existing reference outside the allowed root, to stay on the safe side
|
|
293
|
+
- When `OSV_MCP_ALLOWED_ROOT` is not set, any absolute path can be scanned anyway, so this check is not performed
|
|
292
294
|
|
|
293
|
-
|
|
295
|
+
**Output (success)**
|
|
294
296
|
|
|
295
297
|
```json
|
|
296
298
|
{
|
|
@@ -322,77 +324,77 @@ OSV-Scannerは `pom.xml` の `<parent>` が参照する親POM(`<relativePath>`
|
|
|
322
324
|
}
|
|
323
325
|
```
|
|
324
326
|
|
|
325
|
-
- `packages`
|
|
326
|
-
- `fixed_versions`
|
|
327
|
-
-
|
|
327
|
+
- `packages` is sorted by the most severe vulnerability, and each `vulnerabilities` list by severity (unknown last)
|
|
328
|
+
- `fixed_versions` lists the fixed versions recorded in OSV, in ascending order: by Maven precedence for Maven, Semantic Versioning precedence for npm and Go (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
|
|
329
|
+
- Vulnerabilities without a severity score are reported as `null` / `"unknown"`
|
|
328
330
|
|
|
329
331
|
### `scan_java_artifact`
|
|
330
332
|
|
|
331
|
-
JAR/WAR
|
|
333
|
+
Scans JAR/WAR archives themselves. This is a separate tool from the manifest-based scans.
|
|
332
334
|
|
|
333
335
|
```json
|
|
334
336
|
{ "artifact_path": "/absolute/path/to/application.war" }
|
|
335
337
|
```
|
|
336
338
|
|
|
337
|
-
`artifact_path`
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
`OSV_MCP_ALLOWED_ROOT`
|
|
339
|
+
`artifact_path` is the absolute path to a JAR/WAR file, or to a directory to search.
|
|
340
|
+
A directory search includes `target` and `build`, and excludes `.git`, `node_modules`, `.idea`, `.vscode`, and symbolic links.
|
|
341
|
+
The search is limited to a depth of 8, 100 archives, and 10,000 entries. If the search cannot complete within the limits, `artifact_search_limit_exceeded` is returned instead of silently truncating the results; narrow the target and try again.
|
|
342
|
+
`OSV_MCP_ALLOWED_ROOT` applies as well.
|
|
341
343
|
|
|
342
|
-
OSV-Scanner 2.4.0
|
|
343
|
-
|
|
344
|
+
OSV-Scanner 2.4.0's `java/archive` plugin is used, and nested JARs are also analyzed by the scanner. No Java code is executed and no build is run.
|
|
345
|
+
Identification relies on metadata inside the archives, so dependencies whose metadata was removed, and dependencies inside shaded/minimized JARs, may be missed.
|
|
344
346
|
|
|
345
|
-
|
|
347
|
+
**Reading the output:**
|
|
346
348
|
|
|
347
|
-
-
|
|
348
|
-
- `artifacts[].status`
|
|
349
|
-
-
|
|
350
|
-
- `coverage.completeness`
|
|
351
|
-
- `packages`
|
|
352
|
-
- JAR/WAR
|
|
349
|
+
- `coverage` comes first, with `jars_found`, `jars_identified`, and `unidentified_jars`. The counts are per outer archive found on the file system (including WARs), not the total number of nested JARs.
|
|
350
|
+
- `artifacts[].status` is one of `identified_with_vulnerabilities` / `identified_without_known_vulnerabilities` / `inferred_only` / `unidentified`. "Identified" means that at least one Maven coordinate was found, not that every dependency was identified. `inferred_only` is an archive identified only through inferred coordinates (see below). Even when vulnerabilities are found for it, it is not counted in `jars_identified` and is listed in `unidentified_jars` with a hint (its findings are shown in `identified_vulnerability_count`), because wrong inferred groupIds may hide other vulnerabilities.
|
|
351
|
+
- **Inferred coordinates**: For JARs without `pom.properties` (such as the main Spring Framework JARs), OSV-Scanner infers the Maven coordinates from file names and similar clues, and often gets the groupId wrong (for example `spring-beans:spring-beans` instead of `org.springframework:spring-beans`). Vulnerabilities are not matched for wrong coordinates, so **known vulnerabilities are missed** (for example, Spring4Shell (CVE-2022-22965) in spring-beans 5.3.2 inside the zipkin-server 2.23.2 fat JAR is not detected). Coordinates whose groupId contains no `.` are treated as inferred and reported in `coverage.inferred_coordinates` (count, items, and a warning), and the affected packages are marked `coordinates_inferred: true`. Correct old-style coordinates such as `commons-io:commons-io` are included as well (erring on the safe side). Wrong inferences that contain a `.` (such as `com.sun.jna:jna`) cannot be detected. For accurate results, scan the build's lockfile or `pom.xml` with `scan_project`
|
|
352
|
+
- `coverage.completeness` is always `incomplete`. `identified_vulnerability_count: 0` does not mean the archives are safe.
|
|
353
|
+
- `packages` lists the vulnerable packages that were identified. The same package and vulnerability found in several archives are counted once in the totals.
|
|
354
|
+
- If no JAR/WAR is found, `no_scannable_artifacts` is returned. If no archive can be identified, a successful report with a warning is returned.
|
|
353
355
|
|
|
354
|
-
`suggest_fix`
|
|
355
|
-
|
|
356
|
+
`suggest_fix` remains manifest-based only. Because this tool uses an experimental plugin, the flags and the JAR/WAR output format must be re-verified whenever the pinned OSV-Scanner version is updated.
|
|
357
|
+
Extracting untrusted archives relies on OSV-Scanner's native code. There are timeouts and output limits, but no OS-level memory limit or sandbox is provided.
|
|
356
358
|
|
|
357
359
|
### `scan_sbom`
|
|
358
360
|
|
|
359
|
-
|
|
361
|
+
Looks up the dependencies recorded in an existing SBOM with OSV-Scanner. It does not generate SBOMs, run builds, or execute JARs.
|
|
360
362
|
|
|
361
363
|
```json
|
|
362
364
|
{ "sbom_path": "/absolute/path/to/release-sbom.json" }
|
|
363
365
|
```
|
|
364
366
|
|
|
365
|
-
-
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
-
|
|
367
|
+
- **Supported formats**: UTF-8 JSON CycloneDX 1.4 / 1.5 / 1.6 and SPDX 2.2 / 2.3. XML, SPDX tag-value, and SPDX 3 are not supported.
|
|
368
|
+
- **Input**: the absolute path to a local regular file of 16 MiB or less. Any file name is accepted; the format is detected from the content. CycloneDX requires a `components` array and SPDX a `packages` array. Only the format and main structure are checked, not the full JSON Schema.
|
|
369
|
+
- **Identification**: Include versioned Package URLs in CycloneDX `components[].purl` or SPDX `packages[].externalRefs`, for example `pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1`. See the [OSV-Scanner documentation](https://github.com/google/osv-scanner/blob/main/docs/scan-source.md) for details.
|
|
370
|
+
- **Safe reading**: The allowed root and the size limit (enforced while reading) are checked, and only a private temporary copy is scanned. The original file is never modified, and the copy is deleted on success and failure. If the server is terminated during a scan (SIGTERM/SIGINT/SIGHUP, or the MCP client closing stdin), the copy is deleted and the running OSV-Scanner is stopped before exiting.
|
|
369
371
|
|
|
370
|
-
|
|
372
|
+
`coverage` comes first in the output.
|
|
371
373
|
|
|
372
|
-
- `identified_package_count`:
|
|
373
|
-
- `unidentified_packages`:
|
|
374
|
-
- `status`:
|
|
375
|
-
- `completeness` / `artifact_match`:
|
|
374
|
+
- `identified_package_count`: the number of distinct packages (by name, version, and ecosystem) identified by the scanner, including those without known vulnerabilities.
|
|
375
|
+
- `unidentified_packages`: packages present in the scanner output but missing a version or other information. Items the scanner itself skipped cannot be listed, so an empty array does not mean everything was checked.
|
|
376
|
+
- `status`: `packages_identified` if anything was identified, otherwise `no_packages_identified`.
|
|
377
|
+
- `completeness` / `artifact_match`: both `not_verified`. Whether the SBOM covers every dependency, and whether it matches the actual build, are not verified.
|
|
376
378
|
|
|
377
|
-
`sbom
|
|
379
|
+
`sbom` returns the original file path, the format, the specification version, and the SHA256 of the bytes that were scanned. `identified_vulnerability_count` and `packages` contain the findings for the identified dependencies. **Zero findings does not mean the software is safe.** To cover JARs without metadata, prepare an accurate SBOM for that build.
|
|
378
380
|
|
|
379
|
-
|
|
381
|
+
Invalid JSON returns `invalid_sbom`, an unsupported format `unsupported_sbom_format`, input over the size limit `sbom_too_large`, and a missing or unreadable file `sbom_not_found`. An empty SBOM, or one with no identifiable packages, returns a successful report with a warning. The same timeout, output limit, and concurrency limit as the other tools apply.
|
|
380
382
|
|
|
381
383
|
### `suggest_fix`
|
|
382
384
|
|
|
383
|
-
`scan_project`
|
|
385
|
+
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), and Go. Instead of simply taking the highest fixed version, it picks the fixed version closest to the current release line, falling back through three tiers:
|
|
384
386
|
|
|
385
|
-
| Tier |
|
|
387
|
+
| Tier | Meaning |
|
|
386
388
|
|---|---|
|
|
387
|
-
| `same_minor` |
|
|
388
|
-
| `major_internal` |
|
|
389
|
-
| `cross_major` |
|
|
389
|
+
| `same_minor` | A fixed version in the same release line (the smallest change) |
|
|
390
|
+
| `major_internal` | A fixed version within the same major version (a minor upgrade) |
|
|
391
|
+
| `cross_major` | A major upgrade is needed (may include breaking changes) |
|
|
390
392
|
|
|
391
|
-
npm
|
|
393
|
+
For npm, Go, and PyPI, the "same release line" is the range npm's caret (`^`) treats as compatible (PyPI has no common compatibility rule, but some 0.x packages make breaking changes in minor releases, so the same rule is used; a change of epoch is also `cross_major`). From 1.0.0 the line is major.minor, as for Maven. For 0.x, only the same `0.minor` is the same line, so a minor update (`0.3` → `0.4`) is `cross_major`, and for 0.0.x every update is `cross_major` (SemVer does not guarantee compatibility for 0.x updates).
|
|
392
394
|
|
|
393
|
-
|
|
395
|
+
**Input**: the same as `scan_project` (`project_path`)
|
|
394
396
|
|
|
395
|
-
|
|
397
|
+
**Output (success)**
|
|
396
398
|
|
|
397
399
|
```json
|
|
398
400
|
{
|
|
@@ -407,126 +409,146 @@ npm・Go・PyPIの「同じ系統」は、npmの `^`(キャレット)が互換
|
|
|
407
409
|
"package": "org.apache.logging.log4j:log4j-core",
|
|
408
410
|
"current_version": "2.14.1",
|
|
409
411
|
"ecosystem": "Maven",
|
|
412
|
+
"dependency_relation": "direct",
|
|
410
413
|
"recommended_upgrade": "2.25.4",
|
|
411
414
|
"upgrade_tier": "major_internal",
|
|
412
|
-
"
|
|
413
|
-
"
|
|
415
|
+
"upgrade_note": "…",
|
|
416
|
+
"update_hint": "…",
|
|
417
|
+
"candidate_check": "clean",
|
|
414
418
|
"per_cve_detail": [
|
|
415
|
-
{ "id": "GHSA-jfh8-c2jp-5v3q", "cve": "CVE-2021-44228", "severity": "critical", "fixed_in": "2.15.0", "tier": "major_internal" }
|
|
416
|
-
]
|
|
419
|
+
{ "id": "GHSA-jfh8-c2jp-5v3q", "cve": "CVE-2021-44228", "severity": "critical", "fixed_in": "2.15.0", "tier": "major_internal", "recommended_status": "not_affected" }
|
|
420
|
+
],
|
|
421
|
+
"verification": "verified"
|
|
417
422
|
}
|
|
418
423
|
]
|
|
419
424
|
}
|
|
420
425
|
```
|
|
421
426
|
|
|
422
|
-
- `recommended_upgrade`
|
|
423
|
-
- OSV
|
|
424
|
-
-
|
|
425
|
-
-
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
-
|
|
429
|
-
- `
|
|
430
|
-
- `
|
|
431
|
-
- `
|
|
432
|
-
- `
|
|
433
|
-
|
|
434
|
-
- `
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
-
|
|
438
|
-
-
|
|
439
|
-
-
|
|
440
|
-
|
|
427
|
+
- `recommended_upgrade` is chosen from the known fixed versions: the first candidate, in tier order and then ascending version order, that is confirmed to be outside the affected ranges of every vulnerability being fixed. It does not simply take the highest fixed version per CVE, and it excludes candidates that are affected again in another release line. It does not guarantee that the candidate is the smallest among all published versions, or that it has no undetected vulnerabilities.
|
|
428
|
+
- The OSV affected ranges (`introduced` / `fixed` / `last_affected` / open-ended) are checked: `ECOSYSTEM` ranges by Maven precedence for Maven, `SEMVER` / `ECOSYSTEM` ranges by Semantic Versioning precedence for npm and Go, and `ECOSYSTEM` ranges by PEP 440 for PyPI (non-canonical forms such as `1.8c1` and `2.8.0-rc0` are normalized). Events are sorted by version before evaluation, as in the OSV specification's evaluation algorithm. Commit-based `GIT` ranges are ignored when the same entry has an `ECOSYSTEM` range. Versions explicitly listed in `versions` are also checked (values that are not versions, such as Git tag names, are ignored, since they can never equal a valid candidate). If the information is incomplete (missing, malformed, or unsupported ranges such as `GIT`, `limit`, or range boundaries that cannot be parsed, such as `19.03.9` in some GHSAs or PyTorch's `2.6.0-cu124`), no candidate is assumed to be safe; if no candidate can be verified, `recommended_upgrade: null` and `verification: "no_verified_candidate"` are returned.
|
|
429
|
+
- Pre-releases (`5.0.0-beta.3`, `15.6.0-canary.61`, Go pseudo-versions, PyPI `rc` and `dev` versions; post-releases count as stable) are recommended only when no stable candidate fixes every vulnerability, and are marked `recommended_is_prerelease: true`. A stable version in a higher tier is preferred over a pre-release in a lower tier.
|
|
430
|
+
- When a version is recommended, `verification` is `verified`, and each CVE's `recommended_status` is `affected` / `not_affected` / `unknown` (`not_evaluated` when the recommendation is withheld). `per_cve_detail.fixed_in` is the candidate for that CVE alone; see `recommended_status` for the final recommendation.
|
|
431
|
+
- CVEs with no fixed version newer than the current one get `tier: "unfixed"` and are left out of the recommendation (the data may be incomplete). They are still evaluated against the recommended version, and the result is shown. If every CVE is unfixed, `recommended_upgrade` is `null` as well. A CVE whose fixed version is listed but cannot be parsed as a version (such as `13.0`, which is not SemVer) gets `tier: "unparseable_fix"`; it is not treated as unfixed but stays in the set to be fixed, so the recommendation is withheld (`no_verified_candidate`).
|
|
432
|
+
- Suggestions include an `update_hint`, tailored to whether the package is a direct or transitive dependency:
|
|
433
|
+
- Maven (from `pom.xml`): for a direct dependency, update the `<dependency>` version (or the parent POM, property, or BOM that manages it); for a transitive dependency, override the version in `<dependencyManagement>` or update the direct dependency that requires it
|
|
434
|
+
- npm: for a direct dependency, update the version in the `package.json` listed in `declared_in`; for a transitive dependency, update the direct dependency named in `introduced_by`, or set the version with `overrides` in the root `package.json` (effective only in the root project)
|
|
435
|
+
- Go: `go get <module>@<version>` (also for `// indirect` modules). For a module affected by `replace`, update the version in the `replace` directive instead of `require`. Major versions 2 and later use a different module path (`/v2` and so on) and are separate packages in OSV, so fixes in a newer major line are not included as candidates. If the current version is a pseudo-version (an untagged commit), `upgrade_note` says so
|
|
436
|
+
- PyPI: update the version in `requirements.txt`, `pyproject.toml`, or `Pipfile` and regenerate the lockfile; for a transitive dependency, use a pip constraints file (`-c`) or the override settings of uv or Poetry
|
|
437
|
+
- When the relation is `unknown` or `mixed`, both cases are described (no hint is given for Maven packages from `gradle.lockfile`)
|
|
438
|
+
- **Checking recommended versions against OSV**: Recommendations are verified only against the vulnerabilities found in the scan (those affecting the current version), so a recommended version could be affected by newer vulnerabilities (for example, cryptography 3.2's candidate 49.0.0 is affected by two vulnerabilities introduced in 44.0.0 and fixed in 50.0.0). The server therefore queries `api.osv.dev` about the recommended version, and if it is affected, adds those vulnerabilities to the set to be fixed and chooses again with their fixed versions as candidates (50.0.0 in this example, with the reason in `upgrade_note`). The result is reported in `candidate_check`:
|
|
439
|
+
- `clean`: OSV reports no known vulnerabilities for the recommended version
|
|
440
|
+
- `has_known_vulnerabilities`: no candidate avoids them, so the recommended version has known vulnerabilities (IDs in `recommended_known_vulnerabilities`)
|
|
441
|
+
- `conflict`: OSV reports that a candidate is affected by a vulnerability the scan had evaluated as not affecting it (the range data disagree), and no other candidate is available, so the recommendation is withheld (`recommended_upgrade: null`, `verification: "no_verified_candidate"`)
|
|
442
|
+
- `failed`: the query failed or returned a malformed response (the recommendation, verified against the scanned vulnerabilities, is still returned)
|
|
443
|
+
- `skipped`: the query limit was reached (4 queries per package, 60 per call)
|
|
444
|
+
- `disabled`: disabled with `OSV_MCP_NO_CANDIDATE_CHECK=1`
|
|
445
|
+
|
|
446
|
+
Only packages with a recommendation are queried, and only the package name (already sent during the scan) and the candidate version are sent. A candidate on which OSV and the local range data disagree is never recommended. A malformed response (a response or record that is not an object, or a page token that is not a string) is treated as a failure, not as "no vulnerabilities". Vulnerabilities found this way do not affect the current version, so they are not added to `per_cve_detail`.
|
|
447
|
+
- Each suggestion carries the same `dependency_relation` as `scan_project` (and `introduced_by` / `declared_in` / `replaced_in_go_mod`).
|
|
448
|
+
- `requirements.txt` lines with `>=X` / `~=X` are scanned by OSV-Scanner at the lower bound X. Their suggestions are marked `version_is_lower_bound: true`, and `upgrade_note` explains that the recommendation means raising the lower bound to at least the recommended version (the installed version may differ).
|
|
449
|
+
- Ecosystems without recommendation support (such as RubyGems from an SBOM) return `verification: "unsupported_ecosystem"`, and current versions that cannot be parsed (such as npm git or local path dependencies) return `verification: "unparseable_version"`. In both cases each CVE gets `tier: "unsupported"` and is not counted as unfixed (whether a fixed version exists is not evaluated; check `fixed_versions` in `scan_project` or `explain_vulnerability`).
|
|
450
|
+
- `coverage` is the same as in `scan_project`. If a manifest has no lockfile or a file was excluded, `complete` is `false`, and those dependencies are not included in the suggestions. The `skipped_manifests` / `scope_warning` fields of v0.4.2 and earlier were merged into `coverage.skipped_files` / `coverage.warning`.
|
|
441
451
|
|
|
442
452
|
### `explain_vulnerability`
|
|
443
453
|
|
|
444
|
-
|
|
454
|
+
Returns the details of a vulnerability, given its GHSA or CVE ID, **fetched directly from the OSV database API (api.osv.dev)** (no scan is run). IDs from the scan results can be passed as they are. This is especially useful for vulnerabilities published after the client LLM's knowledge cutoff.
|
|
445
455
|
|
|
446
|
-
|
|
456
|
+
**Input**
|
|
447
457
|
|
|
448
|
-
|
|
|
458
|
+
| Parameter | Type | Description |
|
|
449
459
|
|---|---|---|
|
|
450
|
-
| `vulnerability_id` | string |
|
|
460
|
+
| `vulnerability_id` | string | The vulnerability ID (for example `GHSA-jfh8-c2jp-5v3q` or `CVE-2021-44228`) |
|
|
451
461
|
|
|
452
|
-
|
|
462
|
+
**Output (success)**: `id` / `aliases` / `summary` / `details` (markdown description, up to 4,000 characters) / `severity` (CVSS vectors) / `published` / `modified` / `affected` (affected packages and version ranges) / `references` (URLs of advisories, fix commits, and so on; http/https only, up to 20)
|
|
453
463
|
|
|
454
|
-
>
|
|
464
|
+
> **Note**: OSV's canonical IDs are GHSA and similar IDs, so a CVE ID may not be found (the error message then suggests querying with the GHSA ID).
|
|
455
465
|
|
|
456
|
-
|
|
466
|
+
**Output (error)** — common to all tools
|
|
457
467
|
|
|
458
|
-
|
|
468
|
+
Returns JSON with a machine-readable `kind`, together with `isError: true`:
|
|
459
469
|
|
|
460
470
|
```json
|
|
461
471
|
{
|
|
462
472
|
"error": {
|
|
463
473
|
"kind": "no_manifest_found",
|
|
464
|
-
"message": "
|
|
474
|
+
"message": "…"
|
|
465
475
|
}
|
|
466
476
|
}
|
|
467
477
|
```
|
|
468
478
|
|
|
469
|
-
| kind |
|
|
479
|
+
| kind | Meaning |
|
|
470
480
|
|---|---|
|
|
471
|
-
| `binary_not_found` | OSV-Scanner
|
|
472
|
-
| `project_not_found` |
|
|
473
|
-
| `permission_denied` | Node
|
|
474
|
-
| `no_manifest_found` |
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
479
|
-
| `
|
|
480
|
-
| `
|
|
481
|
-
| `
|
|
482
|
-
| `
|
|
483
|
-
| `
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
488
|
-
| `
|
|
489
|
-
| `
|
|
490
|
-
| `
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
-
|
|
503
|
-
-
|
|
504
|
-
-
|
|
505
|
-
|
|
506
|
-
|
|
481
|
+
| `binary_not_found` | OSV-Scanner was not found (the message includes installation instructions) |
|
|
482
|
+
| `project_not_found` | The path does not exist, or is not a directory or a supported file |
|
|
483
|
+
| `permission_denied` | Node's permission model (`--permission`) does not allow the read or write (the message names the missing permission and the path; see [Running with restricted permissions](#running-with-restricted-permissions)) |
|
|
484
|
+
| `no_manifest_found` | No supported lockfile or manifest was found (for `scan_project`, the message includes how to generate missing lockfiles) |
|
|
485
|
+
| `manifest_search_limit_exceeded` | The manifest search reached its limits (200,000 entries or 1,000 manifests). Specify a narrower directory or the manifest itself |
|
|
486
|
+
| `no_scannable_artifacts` | No JAR/WAR archive was found (`scan_java_artifact`) |
|
|
487
|
+
| `artifact_search_limit_exceeded` | The JAR/WAR search reached its limits (depth 8, 100 archives, 10,000 entries). Narrow the target |
|
|
488
|
+
| `scan_input_too_large` | The total size of the files to scan (the copies in the temporary directory) exceeds the limit (2 GiB). Narrow the target |
|
|
489
|
+
| `sbom_not_found` | The SBOM file does not exist, is not a regular file, or cannot be read |
|
|
490
|
+
| `invalid_sbom` | The SBOM is not valid JSON or lacks the required structure |
|
|
491
|
+
| `unsupported_sbom_format` | The SBOM format or version is not supported |
|
|
492
|
+
| `sbom_too_large` | The SBOM exceeds the size limit (16 MiB) |
|
|
493
|
+
| `binary_download_failed` | The binary download failed (including unsupported platforms) |
|
|
494
|
+
| `binary_checksum_mismatch` | The downloaded binary's checksum does not match (possible tampering or corruption) |
|
|
495
|
+
| `gradle_lockfile_missing` | A Gradle project without `gradle.lockfile` (`scan_java_project`; the message explains how to generate it) |
|
|
496
|
+
| `path_outside_allowed_root` | The path is outside `OSV_MCP_ALLOWED_ROOT` (including when every manifest is excluded because its parent POM references a file outside the allowed root) |
|
|
497
|
+
| `no_packages_found` | No packages to scan (for example a `pom.xml` without dependencies) |
|
|
498
|
+
| `scan_failed` | OSV-Scanner exited abnormally (an excerpt of stderr is in `detail`) |
|
|
499
|
+
| `scan_timeout` | Timeout (120 seconds by default) |
|
|
500
|
+
| `too_many_concurrent_scans` | The concurrent scan limit (2 by default) was reached. Try again after the running scans finish |
|
|
501
|
+
| `output_too_large` | The output exceeds the size limit (32 MB by default) |
|
|
502
|
+
| `invalid_output` | The output could not be parsed as JSON |
|
|
503
|
+
| `invalid_vulnerability_id` | The vulnerability ID is malformed |
|
|
504
|
+
| `vulnerability_not_found` | No vulnerability with the ID exists in the OSV database |
|
|
505
|
+
| `api_request_failed` | The request to the OSV API failed (network, timeout, or a non-2xx response) |
|
|
506
|
+
| `internal_error` | An unexpected error (no internal details are returned) |
|
|
507
|
+
|
|
508
|
+
## Security design
|
|
509
|
+
|
|
510
|
+
The following measures keep the vulnerability scanner itself from becoming an attack vector.
|
|
511
|
+
|
|
512
|
+
- **Supply chain**: The automatic download is limited to the official GitHub Releases and a pinned version, and is verified against **a SHA256 checksum embedded in the package** (the SHA256SUMS file from the release is not trusted, so tampering on the release side is detected). The binary is not made executable until it passes verification, and cached binaries are verified again on every use. `OSV_MCP_PREFER_DOWNLOAD=1` avoids unverified binaries on `PATH`
|
|
513
|
+
- **Command injection**: Commands are run with `spawn` and an argument array, never through a shell. OSV-Scanner receives only a fixed list of arguments, plus verified absolute paths
|
|
514
|
+
- **Snapshots (no gap between checking and reading)**: OSV-Scanner never receives the original files. Lockfiles, `pom.xml` (including the parent POM chain), and JAR/WARs are read safely once, copied into a private temporary directory (accessible only by the owner, deleted afterwards), and the copies are both checked and scanned. Replacing the original files or directories after the check has no effect on the results. Parent POMs are copied into the temporary directory at their original relative positions, so OSV-Scanner can follow relative paths but only finds the verified copies (references that climb out of the temporary directory with repeated `..` are excluded). Reads refuse a symbolic link at the final path component and anything that is not a regular file (so named pipes cannot stall the server), and afterwards the path is resolved again to confirm that it is still inside the boundary and is the same file that was opened. The copies are limited to 2 GiB in total (`scan_input_too_large`). When the server is terminated (SIGTERM/SIGINT/SIGHUP, or stdin closing), the temporary directories are deleted and running OSV-Scanner processes are stopped. Directories left behind by an uncatchable termination such as SIGKILL are deleted at a later startup (only directories whose names exactly match this server's prefixes, owned by the current user, not symbolic links, and unmodified for 24 hours or more)
|
|
515
|
+
- **Path traversal**: Input paths are resolved with `realpath` (following symbolic links) before the boundary check. Manifest searches do not follow symbolic links. OSV-Scanner never receives a directory, only the detected manifests, each with its format given explicitly (given a directory, OSV-Scanner also reads `requirements.txt` files there and follows their `-r ../x.txt` includes outside the scan scope). `requirements.txt` files are not passed as they are: only the dependency lines the server could interpret are normalized and written to a private copy without any include directives, so even if OSV-Scanner's interpretation of includes differs from the server's, files outside the scope are never read. A `pom.xml` whose parent POM chain references a file outside `OSV_MCP_ALLOWED_ROOT` is excluded ([Parent POMs](#parent-poms))
|
|
516
|
+
- **Denial of service**: Timeouts and limits on stdout and on the stderr excerpt. Scanner output is parsed defensively and malformed output does not throw. The number of concurrent scans is limited (2 by default) so that parallel requests cannot start unlimited processes. The server's own analysis reads each file once and reuses the result (lockfiles for npm workspace membership, shared `requirements.txt` includes, parent POMs), with a limit on the total amount read. Responses do not include the internal affected-range data, which keeps large scans to a fraction of their former size
|
|
517
|
+
- **Fail-closed mode**: `OSV_MCP_REQUIRE_ALLOWED_ROOT=1` refuses to start the server when no allowed root is set
|
|
518
|
+
- **Restricted permissions (optional)**: The server works under Node's permission model, reports denied access as `permission_denied`, and warns about missing permissions at startup ([Running with restricted permissions](#running-with-restricted-permissions))
|
|
519
|
+
- **Fixed and documented network destinations**: Dependency resolution is set to `deps.dev` explicitly, and the mode that connects to arbitrary repositories listed in the scanned `pom.xml` (`--data-source native`) is never used (guaranteed by tests). See [Network destinations and privacy](#network-destinations-and-privacy) for the destinations and for `OSV_MCP_NO_REMOTE_RESOLUTION=1`, which stops sending data to deps.dev
|
|
520
|
+
- **Information leakage**: Unexpected exceptions are reduced to `internal_error` without stack traces. Text from external sources (vulnerability summaries and so on) is returned as length-limited, structured data
|
|
521
|
+
- **Prompt injection**: Text from the OSV database (summary / details / IDs and so on) and OSV-Scanner's stderr are sanitized before being returned to the LLM client. Control characters (including ANSI escapes), zero-width characters, bidirectional control characters (such as RLO), Unicode tag characters (invisible text smuggling), and line separators (U+2028/2029) are removed, and NFC normalization is applied. All reads of external data go through a single sanitizing boundary, so nothing is missed
|
|
522
|
+
|
|
523
|
+
## Development
|
|
507
524
|
|
|
508
525
|
```bash
|
|
509
|
-
npm test #
|
|
510
|
-
npx vitest run --coverage #
|
|
511
|
-
npm run typecheck #
|
|
512
|
-
npm run build # dist/
|
|
526
|
+
npm test # run the tests (vitest)
|
|
527
|
+
npx vitest run --coverage # measure coverage
|
|
528
|
+
npm run typecheck # type check
|
|
529
|
+
npm run build # build into dist/
|
|
513
530
|
```
|
|
514
531
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
##
|
|
518
|
-
|
|
519
|
-
- [x] `suggest_fix
|
|
520
|
-
- [x] `explain_vulnerability
|
|
521
|
-
- [x] npm
|
|
522
|
-
- [x] OSV-Scanner
|
|
523
|
-
- [x] Gradle
|
|
524
|
-
- [x] `scan_java_artifact
|
|
525
|
-
- [x] `scan_project
|
|
526
|
-
- [x] `suggest_fix`
|
|
527
|
-
- [x] `suggest_fix`
|
|
528
|
-
- [x]
|
|
529
|
-
|
|
530
|
-
|
|
532
|
+
Design notes and open items are in [docs/DESIGN_TODO.md](docs/DESIGN_TODO.md) (in Japanese). Release notes are in [docs/releases](docs/releases).
|
|
533
|
+
|
|
534
|
+
## Roadmap
|
|
535
|
+
|
|
536
|
+
- [x] `suggest_fix`: recommends the fix closest to the current release line (three-tier fallback)
|
|
537
|
+
- [x] `explain_vulnerability`: vulnerability details (through the OSV API)
|
|
538
|
+
- [x] npm package (`npx osv-scanner-mcp`)
|
|
539
|
+
- [x] Automatic OSV-Scanner download (with checksum verification)
|
|
540
|
+
- [x] Gradle support (lockfile approach)
|
|
541
|
+
- [x] `scan_java_artifact`: JAR/WAR scanning (for projects without a lockfile, or with only shaded/fat JARs)
|
|
542
|
+
- [x] `scan_project`: scans Java / JavaScript / Python / Go lockfiles together
|
|
543
|
+
- [x] `suggest_fix` for JavaScript / Go (SemVer)
|
|
544
|
+
- [x] `suggest_fix` for Python (PEP 440)
|
|
545
|
+
- [x] Direct and transitive dependencies (npm, Go, `requirements.txt`, `pom.xml`)
|
|
546
|
+
- [x] Checking recommended versions against the OSV database
|
|
547
|
+
- [x] Flagging inferred coordinates in JAR/WAR scans
|
|
548
|
+
- [x] Running under Node's permission model
|
|
549
|
+
- [x] English tool descriptions and messages
|
|
550
|
+
- [ ] Direct and transitive dependencies for `gradle.lockfile` and the YAML/TOML lockfiles
|
|
551
|
+
|
|
552
|
+
## License
|
|
531
553
|
|
|
532
554
|
[Apache License 2.0](LICENSE)
|