Note: this is an in-house item already being handled by the maintainer - not open for contribution. Filed for tracking only.
The pnpm v9 lockfile loader classifies packages as dev vs prod with a name-based helper: it aggregates every devDependency name across all importers/workspaces, then marks a package dev if every recorded path's first segment name is in that set.
In a monorepo where the same package is a devDependency in one workspace but a production dependency in another, this wrongly marks the production package (and its production transitives) as dev. Modern pnpm v9 snapshots no longer carry a per-package dev: field, so this name-based helper is the sole classifier and the misclassification is not corrected anywhere.
Impact: under a production-scoped scan (dev findings filtered, or --fail-on scoped to production), a genuinely production dependency that happens to be a devDependency elsewhere in the monorepo is treated as dev and its vulnerabilities are silently suppressed - a false negative.
Reproduction
pnpm v9 lockfile with two importers - packages/web declares lodash as a devDependency, packages/api declares it as a production dependency. lodash is reported as dev even though api depends on it in production. The same happens to production transitives of such a package.
Fix
Classify by graph reachability from production roots, matching pnpm's own semantics: build the set of packages reachable from any importer's dependencies/optionalDependencies by walking the snapshot graph, and mark a reached package dev only when it is not in that set. Unresolved fallback production deps (declared but absent from snapshots) are added to the set directly. Only the v9 loader is affected; the legacy (v5/v6) loader already classifies from the authoritative per-package dev: field. Displayed dependency paths are unchanged (classification-only). The now-unused name-based helper is removed.
Scope: in-house (scanner/parser internals).
The pnpm v9 lockfile loader classifies packages as dev vs prod with a name-based helper: it aggregates every
devDependencyname across all importers/workspaces, then marks a package dev if every recorded path's first segment name is in that set.In a monorepo where the same package is a
devDependencyin one workspace but a production dependency in another, this wrongly marks the production package (and its production transitives) as dev. Modern pnpm v9 snapshots no longer carry a per-packagedev:field, so this name-based helper is the sole classifier and the misclassification is not corrected anywhere.Impact: under a production-scoped scan (dev findings filtered, or
--fail-onscoped to production), a genuinely production dependency that happens to be a devDependency elsewhere in the monorepo is treated as dev and its vulnerabilities are silently suppressed - a false negative.Reproduction
pnpm v9 lockfile with two importers -
packages/webdeclareslodashas a devDependency,packages/apideclares it as a production dependency.lodashis reported as dev even thoughapidepends on it in production. The same happens to production transitives of such a package.Fix
Classify by graph reachability from production roots, matching pnpm's own semantics: build the set of packages reachable from any importer's
dependencies/optionalDependenciesby walking the snapshot graph, and mark a reached package dev only when it is not in that set. Unresolved fallback production deps (declared but absent fromsnapshots) are added to the set directly. Only the v9 loader is affected; the legacy (v5/v6) loader already classifies from the authoritative per-packagedev:field. Displayed dependency paths are unchanged (classification-only). The now-unused name-based helper is removed.Scope: in-house (scanner/parser internals).