diff --git a/src/remediation/npm-registry.ts b/src/remediation/npm-registry.ts index 57ebee10..9bc2ef54 100644 --- a/src/remediation/npm-registry.ts +++ b/src/remediation/npm-registry.ts @@ -175,8 +175,11 @@ export async function resolvePublishedFixVersion( const publishedVersions = Object.keys(packument.versions ?? {}) .filter(looksLikeVersion) + .filter(version => !isPreReleaseVersion(version)) .sort(compareVersions); + const fixedVersionHintIsPreRelease = isPreReleaseVersion(fixedVersionHint); + if (publishedVersions.includes(fixedVersionHint)) { return { resolvedVersion: fixedVersionHint, @@ -193,7 +196,9 @@ export async function resolvePublishedFixVersion( if (nearestPublishedVersion) { return { resolvedVersion: nearestPublishedVersion, - note: `Advisory fixed-version hint ${fixedVersionHint} is not published on npm for ${packageName}; using nearest published version ${nearestPublishedVersion}.`, + note: fixedVersionHintIsPreRelease + ? `Advisory fixed-version hint ${fixedVersionHint} is a pre-release; using nearest published non-pre-release version ${nearestPublishedVersion}.` + : `Advisory fixed-version hint ${fixedVersionHint} is not published on npm for ${packageName}; using nearest published version ${nearestPublishedVersion}.`, verified: true, candidatesChecked: 0, candidatesKnownVulnerable: 0, @@ -204,7 +209,9 @@ export async function resolvePublishedFixVersion( return { resolvedVersion: null, - note: `Advisory fixed-version hint ${fixedVersionHint} is not published on npm for ${packageName}, and no published version >= ${fixedVersionHint} was found.`, + note: fixedVersionHintIsPreRelease + ? `Advisory fixed-version hint ${fixedVersionHint} is a pre-release, and no published non-pre-release version >= ${fixedVersionHint} was found for ${packageName}.` + : `Advisory fixed-version hint ${fixedVersionHint} is not published on npm for ${packageName}, and no published version >= ${fixedVersionHint} was found.`, verified: true, candidatesChecked: 0, candidatesKnownVulnerable: 0, diff --git a/tests/lowest-safe-version.test.ts b/tests/lowest-safe-version.test.ts index 3d200d61..b6ab859e 100644 --- a/tests/lowest-safe-version.test.ts +++ b/tests/lowest-safe-version.test.ts @@ -134,4 +134,32 @@ describe("resolvePublishedFixVersion", () => { expect(result.resolvedVersion).toBe("1.2.4"); expect(result.note).toContain("using nearest published version 1.2.4"); }); + + it("does not return an exact pre-release fix hint (issue #1082)", async () => { + mockPackument(["1.2.3-rc.1", "1.2.3", "1.2.4"]); + + const result = await resolvePublishedFixVersion("tar", "1.2.3-rc.1"); + + expect(result.resolvedVersion).toBe("1.2.3"); + expect(result.note).toContain("using nearest published non-pre-release version 1.2.3"); + expect(result.verified).toBe(true); + }); + + it("does not recommend a higher pre-release when the stable hint is unpublished", async () => { + mockPackument(["1.2.2", "1.2.4-beta.1"]); + + const result = await resolvePublishedFixVersion("tar", "1.2.3"); + + expect(result.resolvedVersion).toBeNull(); + }); + + it("returns null when only pre-release fix candidates are published (issue #1082)", async () => { + mockPackument(["1.2.2", "1.2.3-rc.1", "1.2.4-beta.1"]); + + const result = await resolvePublishedFixVersion("tar", "1.2.3-rc.1"); + + expect(result.resolvedVersion).toBeNull(); + expect(result.note).toContain("no published non-pre-release version"); + expect(result.verified).toBe(true); + }); });