osv-scanner-mcp 0.18.0 → 0.19.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
|
@@ -27,7 +27,11 @@ An MCP server that wraps Google's [OSV-Scanner](https://github.com/google/osv-sc
|
|
|
27
27
|
|
|
28
28
|
## Composer support
|
|
29
29
|
|
|
30
|
-
Pass a project directory, `composer.lock`, or `composer.json` to `scan_project` or `suggest_fix`.
|
|
30
|
+
Pass a project directory, `composer.lock`, or `composer.json` to `scan_project` or `suggest_fix`. Direct/transitive dependency analysis (source checkout) uses the companion `composer.json` in the lockfile's own directory. 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 companion's `require` and `require-dev` are direct dependencies. Packages reachable through the locked packages' `require` are transitive; a dependency's own `require-dev`, `suggest`, and `conflict` are not dependency edges. Direct dependencies include their project-relative `composer.json` in `declared_in`. `introduced_by` names each direct root that transitively requires a package (up to 10, with the remainder in `introduced_by_omitted`). A direct dependency also required by another root remains direct and includes that root in `introduced_by`; cycles do not introduce a package through itself. Transitive dependencies do not have `declared_in`, since the root does not declare them directly.
|
|
33
|
+
|
|
34
|
+
This is name-based graph traversal of the lockfile, not a Composer constraint solver or freshness check. Duplicate installed names, virtual requirements, and references affected by `provide`/`replace` are not guessed; unreachable packages remain `unknown`. Local path and skipped branch entries may connect roots to scanned packages, but their names/metadata are still excluded from scanner input. No path-repository URL or nested package's composer.json is followed. Missing, malformed, symlinked, oversized, or otherwise unreadable companion manifests leave relationships unknown, with a `composer_warnings` note; locked release packages continue to be scanned. The companion is captured safely once (4 MiB per file), shares the Composer 64 MiB input/JSON structure budgets, and traversal/indexing share a 2,000,000-operation budget across all Composer lockfiles. Relationship uncertainty does not itself make dependency scan coverage incomplete.
|
|
31
35
|
|
|
32
36
|
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
37
|
|
|
@@ -308,6 +312,7 @@ Detects the lockfiles and manifests in a project and scans the Java / JavaScript
|
|
|
308
312
|
- `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`
|
|
309
313
|
- `Cargo.lock`: crates.io dependencies immediately required by a local/workspace/path crate are direct; crates.io dependencies reachable through them (including git and alternate-registry origins) are transitive. `introduced_by` names the first direct dependency on each path. References are resolved by exact name, version, and source where provided; ambiguous references are not guessed. Replacements are followed. `declared_in` lists project-relative Cargo.toml paths only when every declaring crate has a unique matching manifest. Otherwise `declaring_crates` names the unmapped declaring crates (up to 10, with the rest in `declaring_crates_omitted`). Dev/build/target-specific distinctions are unavailable. Traversal over 2,000,000 edges returns `unknown`
|
|
310
314
|
- .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
|
|
315
|
+
- Composer (source checkout): the companion root's `require` / `require-dev` are direct, and locked `require` reachability establishes transitive dependencies. `declared_in` names the companion for direct dependencies; `introduced_by` lists transitively requiring direct roots. See [Composer support](#composer-support) for ambiguity and work limits.
|
|
311
316
|
- 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`
|
|
312
317
|
- `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
|
|
313
318
|
- Upgrade recommendations (`suggest_fix`) are available for Maven, npm, PyPI, Go, Rust (crates.io), PHP (Packagist), and .NET (NuGet; source checkout).
|
|
@@ -489,6 +494,7 @@ For npm, Go, PyPI, and Rust, the "same release line" is the range a caret (`^`)
|
|
|
489
494
|
- Suggestions include an `update_hint`, tailored to whether the package is a direct or transitive dependency:
|
|
490
495
|
- 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
|
|
491
496
|
- 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)
|
|
497
|
+
- Composer: for direct dependencies, update the constraint in `declared_in`; for transitive dependencies, update the direct roots in `introduced_by` or their constraints, then regenerate the lockfile with dependency updates allowed, scripts and plugins disabled. PHP and dependency compatibility still require verification.
|
|
492
498
|
- 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
|
|
493
499
|
- 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
|
|
494
500
|
- Rust: where version requirements permit it, `cargo update -p <crate>@<current> --precise <recommended>` updates the lockfile. Otherwise change the direct requirement in the declaring Cargo.toml or update the parent dependency in `introduced_by`. Cross-major upgrades may break compatibility, including 0.x updates. Pre-releases may require explicit `=<version>` requirements and must be allowed by transitive parents. Build metadata is omitted from `--precise`. `[patch.crates-io]` changes a source, not the allowed version range; backports must satisfy parent requirements. Check yanked versions, MSRV, and compiler compatibility. Cargo is never executed by the server
|
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.
|
|
69
|
+
const SERVER_VERSION = "0.19.0";
|
|
70
70
|
const server = new McpServer({
|
|
71
71
|
name: "osv-scanner-mcp",
|
|
72
72
|
version: SERVER_VERSION,
|
|
@@ -88,8 +88,8 @@ server.registerTool("scan_project", {
|
|
|
88
88
|
"a PackageReference Version=\"x\" is looked up at its lower bound (version_is_lower_bound), and references with properties, ranges without a lower bound, floating versions, conditions, or Update/Remove are not looked up (unresolved_package_references). " +
|
|
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
|
-
"Each package's dependency_relation tells whether it is a direct or transitive dependency (determined for package-lock.json, go.mod, requirements.txt,
|
|
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. " +
|
|
91
|
+
"Each package's dependency_relation tells whether it is a direct or transitive dependency (determined for package-lock.json, go.mod, requirements.txt, pom.xml, Cargo.lock, .NET files and Composer; unknown where the format or captured metadata cannot establish it). " +
|
|
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. A safely captured companion composer.json defines direct require/require-dev roots; locked require edges establish introduced_by, and direct declarations use declared_in. Missing/uninterpretable companions, ambiguous providers/replacements or names, and analysis limits leave relationships unknown. 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
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.",
|
|
94
94
|
inputSchema: {
|
|
95
95
|
project_path: z
|
|
@@ -121,7 +121,7 @@ server.registerTool("suggest_fix", {
|
|
|
121
121
|
"(same_minor: same release line -> major_internal: same major version -> cross_major: major upgrade), " +
|
|
122
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). " +
|
|
123
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. " +
|
|
124
|
-
"update_hint explains how to upgrade, depending on whether the package is a direct or transitive dependency (dependency_relation; for npm
|
|
124
|
+
"update_hint explains how to upgrade, depending on whether the package is a direct or transitive dependency (dependency_relation; for npm, Cargo.lock and Composer also introduced_by and declared_in). Composer hints name the declaring manifest for direct dependencies and the introducing roots for transitive dependencies; regenerate the lockfile in a trusted environment and verify constraints. Cargo updates are conditional on parent version requirements; cross-major updates may require Cargo.toml changes. " +
|
|
125
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). " +
|
|
126
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. " +
|
|
127
127
|
"With has_known_vulnerabilities, failed, or skipped, the recommended version has not been confirmed safe; conflict means the data disagreed and the recommendation is withheld. " +
|
package/dist/osv/suggestFix.js
CHANGED
|
@@ -38,6 +38,15 @@ function updateHint(pkg) {
|
|
|
38
38
|
const relation = pkg.dependency_relation;
|
|
39
39
|
const by = pkg.introduced_by?.join(", ");
|
|
40
40
|
switch (pkg.ecosystem) {
|
|
41
|
+
case "Packagist": {
|
|
42
|
+
if (relation === "direct")
|
|
43
|
+
return `Direct dependency. Update its constraint in ${pkg.declared_in?.join(", ") ?? "composer.json"}` +
|
|
44
|
+
(by ? `. It is also required transitively by ${by}, whose constraints may need updating` : "") +
|
|
45
|
+
". Regenerate composer.lock in a trusted environment with Composer scripts and plugins disabled; check PHP, extension and dependency constraints. Composer is not executed by this server";
|
|
46
|
+
if (relation === "transitive")
|
|
47
|
+
return `Transitive dependency. Update ${by ? `the direct dependencies that require it (${by})` : "the direct dependency that requires it"} or adjust their constraints, then regenerate composer.lock with --with-all-dependencies --no-scripts --no-plugins in a trusted environment. Check PHP and extension requirements. Composer is not executed by this server`;
|
|
48
|
+
return UPDATE_HINTS.Packagist;
|
|
49
|
+
}
|
|
41
50
|
case "NuGet": {
|
|
42
51
|
if (/^Microsoft\.(?:NETCore|AspNetCore|WindowsDesktop)\.App\.Runtime\./i.test(pkg.name)) {
|
|
43
52
|
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";
|
|
@@ -166,7 +166,7 @@ export async function buildRelationLookups(copies, maxTotalBytes = MAX_RELATION_
|
|
|
166
166
|
const lookups = new Map();
|
|
167
167
|
const npmWorkBudget = createNpmLockWorkBudget();
|
|
168
168
|
let remaining = maxTotalBytes;
|
|
169
|
-
for (const { copy, format, originalRelative, entries, cargoPackages, declaringManifests, nugetPackages } of copies) {
|
|
169
|
+
for (const { copy, format, originalRelative, entries, cargoPackages, declaringManifests, nugetPackages, composerLookup } of copies) {
|
|
170
170
|
const lockDir = originalRelative === null ? "" : path.dirname(originalRelative);
|
|
171
171
|
let lookup = null;
|
|
172
172
|
try {
|
|
@@ -191,6 +191,9 @@ export async function buildRelationLookups(copies, maxTotalBytes = MAX_RELATION_
|
|
|
191
191
|
else if (format === "packages.lock.json" && nugetPackages !== undefined) {
|
|
192
192
|
lookup = nugetRelations(nugetPackages);
|
|
193
193
|
}
|
|
194
|
+
else if (format === "composer.lock") {
|
|
195
|
+
lookup = composerLookup ?? null;
|
|
196
|
+
}
|
|
194
197
|
}
|
|
195
198
|
catch {
|
|
196
199
|
lookup = null; // 解析できないファイルはunknown(スキャン自体は続ける)
|
|
@@ -324,7 +327,7 @@ export async function scanFromSnapshot(project, options) {
|
|
|
324
327
|
for (const copy of project.composerCopies ?? []) {
|
|
325
328
|
const generated = await snapshot.writeGenerated(copy.content);
|
|
326
329
|
targets.push({ path: generated, format: "composer.lock" });
|
|
327
|
-
relationSources.push({ copy: generated, format: "composer.lock", originalRelative: copy.path });
|
|
330
|
+
relationSources.push({ copy: generated, format: "composer.lock", originalRelative: copy.path, composerLookup: copy.relations });
|
|
328
331
|
}
|
|
329
332
|
if (targets.length === 0)
|
|
330
333
|
return parseOsvScanOutput({ results: [] });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseNpmLock } from "./npmLockCopy.js";
|
|
2
2
|
import { parseComposerVersion } from "./composerVersion.js";
|
|
3
|
+
import { composerLinkNames, composerPackageName } from "./composerRelations.js";
|
|
3
4
|
const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
5
|
/** Only names and release versions reach OSV; repository URLs, scripts, and commit IDs are never forwarded. */
|
|
5
6
|
export function composerLockCopy(text, budget) {
|
|
@@ -9,6 +10,7 @@ export function composerLockCopy(text, budget) {
|
|
|
9
10
|
}
|
|
10
11
|
const issues = [];
|
|
11
12
|
const warnings = [];
|
|
13
|
+
const relationPackages = [];
|
|
12
14
|
let uncertainOrigin = 0;
|
|
13
15
|
// dist.type=pathは自分のコード(monorepoのパッケージ)。Rustのlocal_crates・.NETのlocal_projectsと同じく数だけ返し、範囲の欠けにしない
|
|
14
16
|
let localPackages = 0;
|
|
@@ -17,11 +19,16 @@ export function composerLockCopy(text, budget) {
|
|
|
17
19
|
const packages = [];
|
|
18
20
|
let skipped = 0;
|
|
19
21
|
for (const raw of (data[group] ?? [])) {
|
|
22
|
+
if (record(raw) && composerPackageName(raw.name)) {
|
|
23
|
+
const provides = composerLinkNames(raw.provide), replaces = composerLinkNames(raw.replace);
|
|
24
|
+
relationPackages.push({ name: raw.name, version: typeof raw.version === "string" ? raw.version : "",
|
|
25
|
+
requires: composerLinkNames(raw.require), aliases: provides === null || replaces === null ? null : [...provides, ...replaces] });
|
|
26
|
+
}
|
|
20
27
|
if (record(raw) && record(raw.dist) && raw.dist.type === "path") {
|
|
21
28
|
localPackages++;
|
|
22
29
|
continue;
|
|
23
30
|
}
|
|
24
|
-
if (!record(raw) ||
|
|
31
|
+
if (!record(raw) || !composerPackageName(raw.name) ||
|
|
25
32
|
typeof raw.version !== "string" || parseComposerVersion(raw.version) === null) {
|
|
26
33
|
skipped++;
|
|
27
34
|
continue;
|
|
@@ -36,5 +43,5 @@ export function composerLockCopy(text, budget) {
|
|
|
36
43
|
}
|
|
37
44
|
if (uncertainOrigin > 0)
|
|
38
45
|
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 };
|
|
46
|
+
return { content: JSON.stringify(output), issues, warnings, packageCount: output.packages.length + output["packages-dev"].length, localPackages, relationPackages };
|
|
40
47
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { MAX_DEPENDENCY_EDGES } from "./dependencyRelations.js";
|
|
2
|
+
import { parseNpmLock } from "./npmLockCopy.js";
|
|
3
|
+
export const createComposerRelationBudget = () => ({ remaining: MAX_DEPENDENCY_EDGES });
|
|
4
|
+
const record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
5
|
+
export const composerPackageName = (v) => typeof v === "string" && v.length <= 200 && /^[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/.test(v);
|
|
6
|
+
/** Platform requirements such as php, ext-json and lib-* are not package edges. */
|
|
7
|
+
export function composerLinkNames(value) {
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
return [];
|
|
10
|
+
if (!record(value))
|
|
11
|
+
return null;
|
|
12
|
+
const names = [];
|
|
13
|
+
for (const [name, constraint] of Object.entries(value)) {
|
|
14
|
+
if (typeof constraint !== "string")
|
|
15
|
+
return null;
|
|
16
|
+
const looksLikePackage = name.includes("/");
|
|
17
|
+
if (composerPackageName(name))
|
|
18
|
+
names.push(name);
|
|
19
|
+
else if (looksLikePackage)
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return names;
|
|
23
|
+
}
|
|
24
|
+
/** Analyze only the captured companion manifest, never another root or a path repository's URL. */
|
|
25
|
+
export function composerRootRequirements(text, budget) {
|
|
26
|
+
const root = parseNpmLock(text, budget);
|
|
27
|
+
if (!record(root))
|
|
28
|
+
return null;
|
|
29
|
+
const production = composerLinkNames(root.require), dev = composerLinkNames(root["require-dev"]);
|
|
30
|
+
return production === null || dev === null ? null : [...new Set([...production, ...dev])];
|
|
31
|
+
}
|
|
32
|
+
/** Name-based reachability of the locked solution, not a Composer constraint solver.
|
|
33
|
+
* require-dev is root-only; suggest/conflict/replace/provide are not dependency edges.
|
|
34
|
+
* Provider/replacement ambiguity and duplicate installed names are never guessed.
|
|
35
|
+
*/
|
|
36
|
+
export function composerRelations(packages, roots, budget = createComposerRelationBudget()) {
|
|
37
|
+
const charge = () => --budget.remaining >= 0;
|
|
38
|
+
const byName = new Map();
|
|
39
|
+
const aliased = new Set();
|
|
40
|
+
for (const pkg of packages) {
|
|
41
|
+
if (!charge() || pkg.aliases === null)
|
|
42
|
+
return null;
|
|
43
|
+
byName.set(pkg.name, byName.has(pkg.name) ? null : pkg);
|
|
44
|
+
for (const alias of pkg.aliases) {
|
|
45
|
+
if (!charge())
|
|
46
|
+
return null;
|
|
47
|
+
if (alias !== pkg.name)
|
|
48
|
+
aliased.add(alias);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const resolve = (name) => aliased.has(name) ? null : byName.get(name) ?? null;
|
|
52
|
+
const direct = new Set();
|
|
53
|
+
for (const name of roots) {
|
|
54
|
+
if (!charge())
|
|
55
|
+
return null;
|
|
56
|
+
if (resolve(name))
|
|
57
|
+
direct.add(name);
|
|
58
|
+
}
|
|
59
|
+
const introduced = new Map();
|
|
60
|
+
for (const origin of direct) {
|
|
61
|
+
const visited = new Set([origin]);
|
|
62
|
+
const queue = [origin];
|
|
63
|
+
while (queue.length > 0) {
|
|
64
|
+
if (!charge())
|
|
65
|
+
return null;
|
|
66
|
+
const current = resolve(queue.pop());
|
|
67
|
+
for (const name of current?.requires ?? []) {
|
|
68
|
+
if (!charge())
|
|
69
|
+
return null;
|
|
70
|
+
const target = resolve(name);
|
|
71
|
+
if (!target || visited.has(name))
|
|
72
|
+
continue;
|
|
73
|
+
visited.add(name);
|
|
74
|
+
queue.push(name);
|
|
75
|
+
let sources = introduced.get(name);
|
|
76
|
+
if (!sources)
|
|
77
|
+
introduced.set(name, sources = new Set());
|
|
78
|
+
sources.add(origin);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const results = new Map();
|
|
83
|
+
for (const [name, pkg] of byName) {
|
|
84
|
+
if (!charge())
|
|
85
|
+
return null;
|
|
86
|
+
if (!pkg || aliased.has(name))
|
|
87
|
+
continue;
|
|
88
|
+
const sources = introduced.get(name);
|
|
89
|
+
results.set(JSON.stringify([name, pkg.version]), {
|
|
90
|
+
relation: direct.has(name) ? "direct" : sources ? "transitive" : "unknown",
|
|
91
|
+
...(direct.has(name) ? { declaredIn: new Set(["composer.json"]) } : {}),
|
|
92
|
+
...(sources ? { introducedBy: sources } : {}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return (name, version) => results.get(JSON.stringify([name, version])) ?? { relation: "unknown" };
|
|
96
|
+
}
|
|
@@ -20,6 +20,7 @@ import { analyzeRequirementsFile, createRequirementsReadContext, } from "./requi
|
|
|
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
22
|
import { composerLockCopy } from "./composerLock.js";
|
|
23
|
+
import { composerRelations, composerRootRequirements, createComposerRelationBudget } from "./composerRelations.js";
|
|
23
24
|
/** ファイル名→(エコシステム, 解析形式)。npm-shrinkwrap.jsonはpackage-lock.json形式で解析する */
|
|
24
25
|
const LOCKFILES = new Map([
|
|
25
26
|
["pom.xml", { ecosystem: "Maven", format: "pom.xml" }],
|
|
@@ -306,6 +307,7 @@ async function finalize(projectDir, manifests, markers, allowedRootReal, nugetFi
|
|
|
306
307
|
};
|
|
307
308
|
const readContext = createRequirementsReadContext();
|
|
308
309
|
const composerBudget = createNpmLockWorkBudget();
|
|
310
|
+
const composerRelationBudget = createComposerRelationBudget();
|
|
309
311
|
let composerBytes = MAX_NPM_LOCK_TOTAL_BYTES;
|
|
310
312
|
// A null value permanently marks a crate name with multiple declaration paths.
|
|
311
313
|
const cargoManifests = new Map();
|
|
@@ -330,8 +332,24 @@ async function finalize(projectDir, manifests, markers, allowedRootReal, nugetFi
|
|
|
330
332
|
composerBytes -= read.bytes.length;
|
|
331
333
|
try {
|
|
332
334
|
const parsed = composerLockCopy(read.bytes.toString("utf8"), composerBudget);
|
|
333
|
-
if (parsed.packageCount > 0)
|
|
334
|
-
|
|
335
|
+
if (parsed.packageCount > 0) {
|
|
336
|
+
let relations = null;
|
|
337
|
+
const companion = await readRegularFile(path.join(path.dirname(absolute), "composer.json"), {
|
|
338
|
+
maxBytes: Math.min(4 * 1024 * 1024, composerBytes), boundary: projectDir,
|
|
339
|
+
});
|
|
340
|
+
if (companion.ok) {
|
|
341
|
+
composerBytes -= companion.bytes.length;
|
|
342
|
+
try {
|
|
343
|
+
const roots = composerRootRequirements(companion.bytes.toString("utf8"), composerBudget);
|
|
344
|
+
if (roots !== null)
|
|
345
|
+
relations = composerRelations(parsed.relationPackages, roots, composerRelationBudget);
|
|
346
|
+
}
|
|
347
|
+
catch { /* A companion failure affects relationships, not the scanned lockfile. */ }
|
|
348
|
+
}
|
|
349
|
+
if (relations === null)
|
|
350
|
+
result.composerWarnings.push(`${manifest.path}: Dependency relationships are unknown because its companion composer.json is unavailable/uninterpretable or the dependency analysis limit was reached; locked release packages are still scanned`);
|
|
351
|
+
result.composerCopies.push({ path: manifest.path, content: parsed.content, relations });
|
|
352
|
+
}
|
|
335
353
|
result.skippedFiles.push(...parsed.issues.map((reason) => ({ path: manifest.path, reason })));
|
|
336
354
|
result.composerWarnings.push(...parsed.warnings.map((warning) => `${manifest.path}: ${warning}`));
|
|
337
355
|
result.composerLocalPackages += parsed.localPackages;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "osv-scanner-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
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",
|