Skip to main content
Glama
fairquery

OSV Dependency Vulnerability Check MCP Server

README.md
# Dependency Vulnerability Check for Agents

**Fairquery** Apify Actor that checks open-source dependencies against the free **[OSV.dev](https://osv.dev)** vulnerability database and returns agent-ready rows: one per package (vulnerable or not) and one per vulnerability (CVE, CVSS, severity, affected/fixed versions, references).

## When to use this Actor (LLM / tool selection)

Use it when an agent needs to:

- Answer "is `lodash@4.17.20` / `django==3.2.0` vulnerable, and to what version should I upgrade?"
- Audit a pasted **package.json, package-lock.json, requirements.txt, or go.mod**
- Filter to **HIGH/CRITICAL** issues only for a CI gate or PR comment
- Map OSV / GHSA / PYSEC / GO IDs to **CVE IDs** and CVSS scores

Supported ecosystems: everything OSV supports (npm, PyPI, Go, Maven, crates.io, RubyGems, NuGet, Packagist, Pub, Hex, …). Aliases like `pip`, `golang`, `cargo`, `composer` are accepted.

Do **not** use it for: reachability analysis, license checks, container/OS image scanning, or unpinned version ranges (it checks exact versions).

## Data sources

| Resource | URL |
| --- | --- |
| Batch query | `POST https://api.osv.dev/v1/querybatch` |
| Vulnerability detail | `GET https://api.osv.dev/v1/vulns/{id}` |

Free, no API key. Retries on 429/5xx with exponential backoff; detail fetches are cached and run 8 in parallel.

## Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `packages` | `{ecosystem,name,version}[]` | `[]` | Exact package versions to check |
| `manifestText` | `string` | — | Raw package.json / package-lock.json (v1–v3) / requirements.txt / go.mod |
| `manifestType` | enum | `auto` | Force the manifest format |
| `minSeverity` | `ALL`\|`LOW`\|`MEDIUM`\|`HIGH`\|`CRITICAL` | `ALL` | Minimum severity of emitted vulnerability rows |
| `includeUnknownSeverity` | `bool` | `true` | Keep vulns with no severity data when filtering |
| `includeDevDependencies` | `bool` | `true` | Include dev deps from npm manifests |
| `dedupeAliases` | `bool` | `true` | Collapse GHSA/PYSEC/GO records describing the same issue into one row (GHSA preferred) |
| `maxPackages` | `int` | `1000` | Safety cap on unique packages |

Manifest notes: `package.json` ranges are approximated to their lower bound (`^4.17.20` → `4.17.20`) and a `RANGE_APPROXIMATED` warning row is emitted — prefer the lockfile. `requirements.txt` lines without `==` pins are skipped with a `VERSION_NOT_PINNED` warning (use `pip freeze`).

### Example input

```json
{
  "packages": [
    { "ecosystem": "npm", "name": "lodash", "version": "4.17.20" },
    { "ecosystem": "PyPI", "name": "django", "version": "3.2.0" }
  ],
  "minSeverity": "HIGH"
}
```

## Output

Every row has `schema_version` (`1.0.0`), `type` (`package` | `vulnerability` | `error`), `source_url` (OSV API URL), and `checked_at`.

### Package row

```json
{
  "type": "package", "ecosystem": "npm", "name": "lodash", "version": "4.17.20",
  "purl": "pkg:npm/lodash@4.17.20", "vulnerable": true, "vuln_count": 3, "vuln_count_total": 5,
  "vuln_ids": ["GHSA-r5fr-rjxr-66jc", "GHSA-35jh-r3h4-6jhm", "GHSA-29mw-wpgm-hmr9"],
  "max_severity": "HIGH", "max_cvss_score": 8.1,
  "fixed_versions": ["4.18.0", "4.17.21"], "recommended_version": "4.18.0"
}
```

`vuln_count` is after the severity filter and alias de-duplication; `vuln_count_total` is the raw OSV count. `recommended_version` is the smallest version that fixes every emitted vuln (best-effort, null if some vuln has no fix).

### Vulnerability row

Fields: `id`, `package_name`, `package_version`, `aliases`, `cve_ids`, `summary`, `details` (≤2000 chars), `severity`, `cvss_score` (computed CVSS v3 base score), `cvss_vector`, `cvss_version`, `severity_source` (`cvss` | `database_specific` | `alias` | `none`), `severity_alias_id`, `cwe_ids`, `affected_ranges[]` (`introduced`/`fixed`/`last_affected`), `affected_versions_sample`, `fixed_versions`, `published`, `modified`, `references[]`, `osv_url`.

Severity: CVSS v3 base score → NONE/LOW/MEDIUM/HIGH/CRITICAL; otherwise the advisory's label (GHSA `MODERATE` → `MEDIUM`); otherwise borrowed from a GHSA alias; otherwise `UNKNOWN` (common for some Go advisories).

### Error / warning rows (machine-readable)

```json
{ "type": "error", "code": "OSV_VULN_FETCH_FAILED", "message": "OSV /vulns/GHSA-… -> HTTP 503", "retryable": true, "vuln_id": "GHSA-…" }
```

Codes: `INPUT_EMPTY`, `INVALID_PACKAGE`, `MANIFEST_UNDETECTED`, `MANIFEST_PARSE_FAILED`, `MAX_PACKAGES_EXCEEDED`, `OSV_QUERYBATCH_FAILED`, `OSV_VULN_FETCH_FAILED`; warnings (with `context.severity = "warning"`): `RANGE_APPROXIMATED`, `VERSION_NOT_PINNABLE`, `VERSION_NOT_PINNED`.

### Run summary

Key-value store record **`RUN_SUMMARY`**: packages checked/vulnerable, vulnerabilities emitted/filtered/deduplicated, severity counts, OSV request count, echoed input.

## Pricing (pay per event)

| Event | Price | When |
| --- | --- | --- |
| `apify-actor-start` | $0.00005 | Once per run (Apify default start event) |
| `package-checked` | $0.0009 | Each package row (vulnerable or clean) |
| `vulnerability-found` | $0.0028 | Each emitted vulnerability row (after severity filter & alias de-dup) |

Error and warning rows are **never** charged. Example: lodash + django (2 packages, 41 vulnerabilities) ≈ $0.117. Set a max charge per run in run options to cap spend; the Actor stops cleanly at the limit.

## Memory / runtime

Default **256 MB**, timeout 600 s. HTTP only (no browser). A 2-package run finishes in ~5–10 s.

## MCP / Apify tools

Call from agents via [Apify MCP](https://mcp.apify.com):

1. Connect an MCP client to `https://mcp.apify.com` with your Apify token.
2. Run actor `fairquery/osv-dependency-vulnerability-check`.
3. Read the default dataset (filter `type == "vulnerability"`) and the `RUN_SUMMARY` record.

```json
{
  "actor": "fairquery/osv-dependency-vulnerability-check",
  "input": { "manifestText": "django==3.2.0\nrequests==2.25.0", "minSeverity": "HIGH" }
}
```

## Local stdio MCP server

This repo also ships a **stdio MCP server** (TypeScript, `@modelcontextprotocol/sdk`) under [`mcp/`](./mcp). It exposes one tool, `check_dependency_vulnerabilities`, which runs this Actor through the Apify API. `initialize` and `tools/list` work **without** a token; tool calls require `APIFY_TOKEN` (calls are billed per the pricing above).

```bash
cd mcp && npm install && npm run build
node scripts/smoke-test.mjs          # initialize + tools/list, no token
APIFY_TOKEN=... node scripts/smoke-test.mjs --call   # real tool call
```

Claude Desktop / Cursor config:

```json
{
  "mcpServers": {
    "fairquery-osv": {
      "command": "node",
      "args": ["/path/to/osv-dependency-vulnerability-check/mcp/dist/index.js"],
      "env": { "APIFY_TOKEN": "<your Apify token>" }
    }
  }
}
```

Docker: `docker build -f Dockerfile.mcp -t fairquery-osv-mcp .` (build context = repo root; also the Dockerfile to paste into Glama). `glama.json` lists `fairquery` as maintainer.

## Links

- Apify Store: https://apify.com/fairquery/osv-dependency-vulnerability-check
- GitHub: https://github.com/fairquery/osv-dependency-vulnerability-check
- License: MIT

## Local development

```bash
npm install
# put input in storage/key_value_stores/default/INPUT.json
npx apify run
npm test
```

## Disclaimer

Data comes from OSV.dev (aggregating GitHub Advisory Database, PyPA, Go Vulnerability DB and others) under their respective licenses. No warranty of completeness; absence of results does not prove a package is safe.