Repository navigation
pnpm workspace importers add phantom components with the semver specifier as version #339
Description
Summary
On pnpm workspaces, hooray scan project adds phantom inventory components whose version is the declared semver specifier (for example ^1.0.0) instead of the resolved version. Those phantom components produce false OSV matches for old advisories that the resolved version does not have.
Classification
Bug
Version and provenance
- Hooray:
0.9.0 - Linux x64 release asset SHA-256:
c1bc87e4a2382d957066b01d15d12543025776f1272266ff3e9047bdad55c923 - Platform: Linux x86_64
- Follow-up to fix(parsers): accept multi-document pnpm-lock.yaml #337 (multi-document
pnpm-lock.yaml, fixed in 0.9.0) - The real-world application source is private; the generic fixture below reproduces the defect without it
Reproduction
Create a workspace with pnpm-workspace.yaml:
packages:
- 'apps/*'Root package.json:
{
"name": "probe",
"private": true,
"dependencies": {
"is-odd": "^3.0.1"
}
}apps/web/package.json:
{
"name": "web",
"private": true,
"dependencies": {
"is-even": "^1.0.0"
}
}Two-document pnpm-lock.yaml (the shape pnpm 12 writes) with both importer entries and resolved packages for is-odd@3.0.1 and is-even@1.0.0.
Run:
hooray scan project . --policy hooray-policy.yaml --offline --format jsonExpected and actual behavior
Expected: the inventory contains exactly the resolved components: is-odd@3.0.1 and is-even@1.0.0.
Actual: the inventory additionally contains is-even with the literal version ^1.0.0:
[
{ "name": "is-even", "version": "1.0.0" },
{ "name": "is-even", "version": "^1.0.0" },
{ "name": "is-odd", "version": "3.0.1" }
]On a real workspace, every such specifier-versioned phantom matched historical advisories, inflating ten true vulnerability findings into 83, including CVEs fixed years before the resolved release (for example a package resolved to 19.2.8 flagged with a 2018 advisory via its ^19.2.6 phantom).
Evidence and scope
The minimal run is offline and dependency-free. The phantom appears only for dependencies of workspace importers (apps/web), not for the root importer. A single-document lockfile with the same content does not show the phantom in earlier releases because the scan aborted outright (#337).
This issue covers inventory construction from workspace importers; OSV matching against the phantom versions is the visible consequence.
Acceptance criteria
- The fixture inventory contains only resolved versions; no component carries a range specifier as its version.
- OSV matching runs against resolved versions only.
- The real-world scan described above returns to the ten true findings shared with the reference scanner.
- Regression tests cover workspace importers in two-document pnpm 12 lockfiles.
Activity
wakemeup0 commentedon Sep 22, 2026
This was generated by AI during triage.
Agent brief
Behavior and contract:
pnpm-lock.yamlas plain specifier strings (is-even: ^1.0.0) while the root importer keeps{specifier, version}objects.pnpm_resolved_str(src/parsers/pnpm.rs:302-326) falls through toSome((dep, text))for any bare string without@/://, so the specifier becomes a component version: phantomis-even@^1.0.0alongside the resolvedis-even@1.0.0frompackages:.Invariants:
packages:remain supported for concrete versions (src/parsers/pnpm.rs:103-108).link:/workspace:/file:/portal:/tarball:specifiers still produce no component.Acceptance criteria:
Non-goals:
packages:/snapshots:entries (first recorded version, matchingresolve_lock_componentfallback semantics).pnpm_dep_targetalready falls back to name lookup).Evidence and exact reproduction:
src/parsers/pnpm.rs:322-325(bare-string fallthrough) at base7f706fe.pnpm-lock.yaml— doc 1importers:with root.object deps andapps/webplain-string specifier deps; doc 2settings/packages/snapshots.hooray scan project . --offline --format jsonyieldsis-even@^1.0.0phantom.npm:/link:/tarball phantoms but not bare range specifiers.Risks and blockers:
latest) and wildcards (1.x) should resolve by name, not become versions.packages:) yields no component rather than a specifier-versioned one.