GitHub vulnerability alerts: fix versions from different major lines are merged, producing a cross-major bump #45067
Unanswered
a544jh
asked this question in
Request Help
Replies: 0 comments
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?
GitHub.com
Which version of Renovate are you using?
main@190f8d5(2026-08-04)Please tell us more about your question or problem
AI assistance disclosure: the tests and this write-up were produced with Claude Code (model
claude-opus-5). The hypothesis and investigation plan were mine; Claude wrote and ran the tests, verified the advisory and npm metadata, and drafted the text. I've reviewed the output and conclusions.Tests:
a544jh/renovate@claude/renovate-dependabot-grouping-bug-let95a(specs only, no source changes)Summary
When one package is affected in several disjoint major lines,
detectVulnerabilityAlerts()merges its alerts into a single rule keeping the highestfirst_patched_version. That becomes a hard floor for every dependency of that name, so a dependency on a lower major line is bumped across a major boundary to a version its consumers never asked for — when a fix existed on its own line.vulnerable_version_rangeis parsed off the alert payload but never used to scope the rules. The OSV path handles the same case correctly.Distinct from #42760, which maintainers have called intended. The claim isn't that alerts override config — it's that the version picked belongs to the wrong major line.
What it looks like
A workspace pins
brace-expansionunderminimatch@3(which declares^1.1.7); other chains in the repo resolve^2and^5. GitHub raises one alert per line. Renovate opensUpdate dependency brace-expansion@^1 to v5 [SECURITY]:The key still says
^1; the value no longer satisfies it. The correct fix for that line was1.1.16.Reproduction
Characterization tests, passing on
main. GHSA-3jxr-9vmj-r5cp has three ranges:< 1.1.16→1.1.16,>= 2.0.0, < 2.1.2→2.1.2,>= 3.0.0, < 5.0.7→5.0.7.Three alerts → one rule (
prBodyNotesomitted):[{ "matchDatasources": ["npm"], "matchPackageNames": ["brace-expansion"], "matchCurrentVersion": "< 5.0.7", "vulnerabilityFixVersion": "5.0.7", "vulnerabilitySeverity": "LOW", "isVulnerabilityAlert": true, "force": { "enabled": true } }]That rule, on a dependency at
1.1.15(vulnerabilityFixStrategyat its defaultlowest):[{ "bucket": "major", "newVersion": "5.0.7", "newValue": "5.0.7", "newMajor": 5, "newMinor": 0, "newPatch": 7, "hasAttestation": false, "updateType": "major", "isBreaking": true }]1.1.16is in the release list — the floor removes it beforelowestruns, so "lowest" means "lowest at or above5.0.7". A control test confirms the same lookup withvulnerabilityFixVersion: '1.1.16'returns1.1.16as a patch.OSV, same advisory, same dependency — scoped to its own line (
forceandprBodyNotesomitted):[{ "matchDatasources": ["npm"], "matchPackageNames": ["brace-expansion"], "matchCurrentVersion": "1.1.15", "versioning": "npm", "allowedVersions": ">= 1.1.16", "isVulnerabilityAlert": true }]Mechanism
init/vulnerability.ts— bucket key iscombinedAlerts[ecosystem][depName]; no version, no manifest. Merge keeps the max viaisGreaterThan. One rule per bucket, scoped only bymatchPackageNamesandmatchCurrentVersion: '< ' + firstPatchedVersion.process/lookup/index.ts—vulnerabilityFixVersionis a hard floor, applied beforevulnerabilityFixStrategypicks among survivors.platform/github/schema.tsparsesvulnerable_version_rangeandmanifest_path; the former is read once ingithub/index.tsfor a debug log. Neither reachesdetectVulnerabilityAlerts().process/vulnerabilities.ts(OSV) gates eachaffectedonisPackageVulnerable(depVersion, affected);getFixedVersion()takes the lowestfixedabove the dependency's current version.Observed vs inferred
Observed: all test outputs above; the advisory ranges.
Inferred: how often GitHub emits separate alerts per major line rather than coalescing upstream — I reproduced from the alert payload inward, not from a live Dependabot feed. The merge is unconditional either way.
Possible fix
vulnerable_version_rangein the bucket key, emit one rule per range, and setmatchCurrentVersionfrom the range rather than< fixVersion.affectedmatching the OSV path already uses.Logs (if relevant)
No response
All reactions