Repository navigation
npm workspaces: lockedVersion is null for hoisted deps, silently disabling OSV vulnerability detection #45331
Unanswered
sleukeleire
asked this question in
Request Help
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
How are you running Renovate?
Self-hosted Renovate CLI
Which platform you running Renovate on?
GitLab (.com or self-hosted)
Which version of Renovate are you using?
44.32.6
Please tell us more about your question or problem
npm workspaces:
lockedVersionis null for hoisted deps, silently disabling OSV vulnerability detectionDescribe the bug
In an npm workspaces monorepo with a single root
package-lock.json(lockfileVersion 3), Renovateresolves
lockedVersionfor a workspace package's dependency only if npm happened to nest thatdependency under
<workspace>/node_modules/. For dependencies npm hoisted to the rootnode_modules/,lockedVersionisnull, even though the version is unambiguously present in thelockfile.
On its own this is a minor annoyance. Combined with
osvVulnerabilityAlerts, it silently disablesvulnerability detection for the large majority of dependencies, because
lib/workers/repository/process/vulnerabilities.tsrequires a concrete version:With
lockedVersion === nullthe fallback is the range (^9.39.4), which is not a version, so thepackage is skipped. The run reports success and creates no MRs.
Renovate version
44.32.6(reproduced withrenovate --platform=local)Repository setup
--platform=local)workspaces: ["packages/*"], one rootpackage-lock.json,lockfileVersion: 3Root cause
lib/modules/manager/npm/extract/post/locked-versions.tsbuilds the lookup key from the packagefile's directory relative to the lockfile, with no fallback:
And
lib/modules/manager/npm/schema.tsnormalises lockfile keys by stripping only a leadingnode_modules/:So a hoisted entry
node_modules/eslintbecomes keyeslint, while the lookup asks forpackages/deployer/node_modules/eslint. No match, no fallback,lockedVersion = null.Reproduction / evidence
Lockfile contents (via
python3, keys ending in the dep name):What Renovate extracted:
lockedVersionpackage.json(root)esbuildnode_modules/esbuild"0.28.1"✅packages/deployer/package.jsonchokidarpackages/deployer/node_modules/chokidar"5.0.0"✅packages/deployer/package.jsoneslintnode_modules/eslint(hoisted)null❌packages/react-component-library/package.jsonreactnode_modules/react(hoisted)null❌packages/react-component-library/package.json@types/nodepackages/react-component-library/node_modules/@types/node"20.19.43"✅The pattern is exactly "did npm nest it or hoist it", which should be irrelevant to determining the
installed version.
Impact with
osvVulnerabilityAlerts: true49
Skipping vulnerability lookuplines in a single run. The skipped set is not random — these areall packages that have OSV advisories, i.e. Renovate looked them up in the DB and then could not
determine which version is installed:
The only package file where vulnerability detection works end-to-end is one that has its own
package-lock.jsonin the same directory (relativeDir === ''), where four advisories werecorrectly found on
body-parserandexpress.Net effect: a repo configured for security-only updates receives alerts for a small minority of its
dependency tree, with no error, no warning, and no indication in the Dependency Dashboard.
Expected behaviour
For a workspace package file, if there is no nested
<relativeDir>/node_modules/<dep>entry,Renovate should fall back to the hoisted root entry — which is what npm's own resolution does.
Suggested fix
Nested wins where present, so no existing behaviour changes; only the currently-
nullcases areaffected.
Workarounds attempted (both unsuccessful)
npm install --package-lock-onlyproduced only cosmetic workspaceversionstring changes. The entries were already correct; the lookup is the problem.currentVersionfill the gap — removed thepackageRules: [{matchPackageNames: ["*"], enabled: false}]rule so every dep would be looked up, hoping the secondfetchVulnerabilities()pass would use
dep.currentVersion. No change: still 49 skips and the same four advisories.currentVersionis not populated for range-only deps.Anything else?
engines,packageManagerandvoltadepTypes are explicitly skipped before locked-versionresolution, which is expected and unrelated — but it does mean
engines.npmalways appears in theskip list, adding noise to the diagnosis.
Logs (if relevant)
Logs
All reactions