Skip to content

fix(remediation): nearest-published-version path can recommend a pre-release below the fix #1082

Description

@sonukapoor

Follow-up to #1077, split out so that issue stays scoped to the comparator itself.

Problem

resolveLowestKnownNonVulnerableVersion has a second code path that does not filter pre-releases, so it can recommend a version that predates the fix.

src/remediation/npm-registry.ts:176-178 sorts every published version with no pre-release filter:

const publishedVersions = Object.keys(packument.versions ?? {})
  .filter(looksLikeVersion)
  .sort(compareVersions);

Line 192 then picks the nearest version at or above the advisory's fixed-version hint:

const nearestPublishedVersion = publishedVersions.find(version => compareVersions(version, fixedVersionHint) >= 0) ?? null;

Reproduced against the real comparator:

published:           ["1.2.0","1.2.2","1.2.3-rc.1","1.2.4","1.3.0"]
advisory fixed hint: 1.2.3
-> recommends:       1.2.3-rc.1

1.2.3-rc.1 is below 1.2.3 under semver §11.3, so it does not contain the fix. The CLI emits a copy-and-run upgrade command that does not remediate, along with a note presenting it as the nearest published version. This directly contradicts the actionable-output principle: a wrong command is worse than no command.

Note the sibling path at line 261 already gets this right with .filter(v => !isPreReleaseVersion(v)). Only this earlier branch is missing it.

Scope: this affects online mode

#1077's detection false negative is offline-only, because online detection goes through OSV querybatch and the npm bulk advisory endpoint, both server-side and semver-correct. This issue is different: remediation runs against the npm registry for everyone, so a bad fix recommendation reaches all users regardless of --offline.

Relationship to #1077

Fixing the comparator in src/utils/version.ts corrects the ordering but does not fix this on its own. Once compareVersions is semver-correct, 1.2.3-rc.1 >= 1.2.3 becomes false, so .find() would skip it and land on 1.2.4, which happens to be right here. But that is incidental: the path still has no explicit intent to exclude pre-releases, so an advisory whose fixed value is itself a pre-release would still select one. The filter should be explicit rather than emergent.

Sequence: land #1077 first, then this, then re-run the check above.

Suggested fix

Apply the same .filter(v => !isPreReleaseVersion(v)) used at line 261 to the publishedVersions list, and decide deliberately what should happen when the advisory's own fixed value is a pre-release. Add a regression test pinning that a pre-release is never returned as a resolved fix version.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions