Skip to content

Commit 1c63744

Browse files
committed
fix: pick correct fixed version
1 parent 6334e74 commit 1c63744

File tree

1 file changed

+43
-11
lines changed

1 file changed

+43
-11
lines changed

server/utils/dependency-analysis.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import type {
99
VulnerabilityTreeResult,
1010
DeprecatedPackageInfo,
1111
OsvAffected,
12+
OsvRange,
1213
} from '#shared/types/dependency-analysis'
1314
import { mapWithConcurrency } from '#shared/utils/async'
1415
import { resolveDependencyTree } from './dependency-resolver'
16+
import * as semver from 'semver'
1517

1618
/** Maximum concurrent requests for fetching vulnerability details */
1719
const OSV_DETAIL_CONCURRENCY = 25
@@ -116,7 +118,7 @@ async function queryOsvDetails(pkg: PackageQueryInfo): Promise<PackageVulnerabil
116118
severity,
117119
aliases: vuln.aliases || [],
118120
url: getVulnerabilityUrl(vuln),
119-
fixedIn: getFixedVersion(vuln.affected, pkg.name),
121+
fixedIn: getFixedVersion(vuln.affected, pkg.name, pkg.version),
120122
})
121123
}
122124

@@ -147,26 +149,56 @@ function getVulnerabilityUrl(vuln: OsvVulnerability): string {
147149
}
148150

149151
/**
150-
* Extract the earliest fixed version for a specific package from vulnerability data.
151-
* Returns the first 'fixed' event found in the affected ranges for the given package.
152+
* Check if a version falls within an OSV range (between introduced and fixed).
153+
* OSV ranges use events: introduced starts vulnerability, fixed ends it.
154+
*/
155+
function isVersionInRange(version: string, range: OsvRange): boolean {
156+
const introduced = range.events.find(e => e.introduced)?.introduced
157+
const fixed = range.events.find(e => e.fixed)?.fixed
158+
159+
if (!introduced) return false
160+
161+
// Handle "0" as "0.0.0" for semver comparison
162+
const introVersion = introduced === '0' ? '0.0.0' : introduced
163+
164+
try {
165+
// Version must be >= introduced AND < fixed (if fixed exists)
166+
return semver.gte(version, introVersion) && (!fixed || semver.lt(version, fixed))
167+
} catch {
168+
// If semver parsing fails, skip this range
169+
return false
170+
}
171+
}
172+
173+
/**
174+
* Extract the fixed version for a specific package version from vulnerability data.
175+
* Finds the range that contains the current version and returns its fixed version.
176+
* @see https://ossf.github.io/osv-schema/#affectedrangesevents-fields
152177
*/
153178
function getFixedVersion(
154179
affected: OsvAffected[] | undefined,
155180
packageName: string,
181+
currentVersion: string,
156182
): string | undefined {
157183
if (!affected) return undefined
158184

159-
// Find the affected entry for this specific package
160-
const packageAffected = affected.find(
185+
// Find all affected entries for this specific package
186+
const packageAffectedEntries = affected.filter(
161187
a => a.package.ecosystem === 'npm' && a.package.name === packageName,
162188
)
163-
if (!packageAffected?.ranges) return undefined
164189

165-
// Look through ranges to find a 'fixed' event
166-
for (const range of packageAffected.ranges) {
167-
for (const event of range.events) {
168-
if (event.fixed) {
169-
return event.fixed
190+
// Check each entry's ranges to find one that contains the current version
191+
for (const entry of packageAffectedEntries) {
192+
if (!entry.ranges) continue
193+
194+
for (const range of entry.ranges) {
195+
// Only handle SEMVER ranges (most common for npm)
196+
if (range.type !== 'SEMVER') continue
197+
198+
if (isVersionInRange(currentVersion, range)) {
199+
// Found the matching range - return its fixed version
200+
const fixedEvent = range.events.find(e => e.fixed)
201+
if (fixedEvent?.fixed) return fixedEvent.fixed
170202
}
171203
}
172204
}

0 commit comments

Comments
 (0)