feat: show fixed version for vulnerabilities#967
feat: show fixed version for vulnerabilities#967Flo0806 wants to merge 1 commit intonpmx-dev:mainfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
📝 WalkthroughWalkthroughThe PR adds functionality to display the fixed version for vulnerabilities throughout the application. Changes include a Vue component modification to render a fixed-in version indicator next to each vulnerability, updates to 30+ internationalisation and localisation files adding a Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
shared/types/dependency-analysis.ts (1)
39-83:⚠️ Potential issue | 🟠 MajorType definitions do not enforce OSV schema constraints; strict type-safety required.
The
OsvRangeEventinterface allows zero or multiple fields simultaneously, but the official OSV schema mandates exactly one of:introduced,fixed,last_affected, orlimit. Additionally,OsvRangeis missing the requiredrepofield (mandatory whentypeis'GIT'), and theeventsarray constraint—at least oneintroducedevent must be present—is not enforced. Use a discriminated union or more precise typing to reflect these constraints and ensure strict type-safety at the API boundary per coding guidelines.
| /** | ||
| * Extract the earliest fixed version for a specific package from vulnerability data. | ||
| * Returns the first 'fixed' event found in the affected ranges for the given package. | ||
| */ | ||
| function getFixedVersion( | ||
| affected: OsvAffected[] | undefined, | ||
| packageName: string, | ||
| ): string | undefined { | ||
| if (!affected) return undefined | ||
|
|
||
| // Find the affected entry for this specific package | ||
| const packageAffected = affected.find( | ||
| a => a.package.ecosystem === 'npm' && a.package.name === packageName, | ||
| ) | ||
| if (!packageAffected?.ranges) return undefined | ||
|
|
||
| // Look through ranges to find a 'fixed' event | ||
| for (const range of packageAffected.ranges) { | ||
| for (const event of range.events) { | ||
| if (event.fixed) { | ||
| return event.fixed | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return undefined | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
OSV (Open Source Vulnerabilities) schema affected.ranges.events ordering specification
💡 Result:
In the OSV schema, affected[].ranges[].events is defined as a timeline of status changes (each entry is exactly one of introduced, fixed, last_affected, or limit). There is no hard requirement that the JSON array be pre-sorted, but:
- The schema recommends keeping
eventssorted according to the ordering defined byaffected[].ranges[].type(“While not required, it’s also recommended to keep theeventsarray sorted according to theaffected[].ranges[].typeof the range.”). [1] - The reference evaluation pseudocode explicitly iterates
for evt in sorted(range.events), i.e., consumers should sort events (by the range type’s version ordering) before applying them. [1]
So, ordering is recommended (producer-side) and effectively assumed/handled (consumer-side) via sorting, using the comparison semantics of the range type (e.g., SemVer precedence for SEMVER). [1]
Source: OSV Schema spec, “affected[].ranges[].events fields” and “Evaluation” sections. [1]
🏁 Script executed:
# Check the imports and dependencies in the file
head -50 server/utils/dependency-analysis.ts
# Check for semver dependency in package.json
cat package.json | grep -A 5 -B 5 semver || echo "semver not found in package.json"
# Check if semver is imported elsewhere in the project
rg "import.*semver" --type ts --type tsxRepository: npmx-dev/npmx.dev
Length of output: 2316
🏁 Script executed:
# Look for usage of getFixedVersion
rg "getFixedVersion" --type ts -A 3 -B 3
# Check full context of the function and surrounding code
sed -n '140,180p' server/utils/dependency-analysis.ts
# Look for tests or documentation about how fixed versions are used
fd "*.test" "*.spec" server/utils/ | head -5
find . -name "*.md" -type f | xargs grep -l "fixed.*version" 2>/dev/null | head -5Repository: npmx-dev/npmx.dev
Length of output: 2634
🌐 Web query:
semver.compare() function documentation and return value semantics
💡 Result:
In the npm semver (node-semver) library, semver.compare(v1, v2[, options]) compares two SemVer version strings and returns an ordering value suitable for sorting.
Documentation
compare(v1, v2): returns0if equal,1ifv1is greater,-1ifv2is greater; sorts ascending when used as anArray.sort()comparator. [1][2]
Return value semantics
0→v1andv2are semver-equal1→v1>v2-1→v1<v2(i.e.,v2>v1) [1][2]
Options / strictness
- Functions accept an optional final
optionsobject (or historically a boolean interpreted as{ loose: true }), wherelooseenables more forgiving parsing. [1]
Errors (invalid input)
- If either version can’t be parsed as a valid SemVer, the underlying parser can throw
TypeError: Invalid Version: .... [3]
Sources: [1] [2] [3]
🏁 Script executed:
# Check if there are any mixed range types or edge cases in actual OSV data handling
rg "range\.type" server/utils/dependency-analysis.ts -A 2 -B 2
# Check if there are tests or examples showing what data looks like
rg "ranges\|events" --type ts server/ | grep -i "test\|spec\|mock\|example" | head -10Repository: npmx-dev/npmx.dev
Length of output: 43
🌐 Web query:
OSV spec examples multiple fixed versions across ranges npm ecosystem
💡 Result:
In OSV, the way to express multiple disjoint vulnerable ranges with different “fixed” versions (common in the npm/semver ecosystem when multiple maintenance branches exist) is to include multiple ranges timelines (or, equivalently, multiple affected[] entries for the same package).
Real npm example (from OSV): minimist has two fixed versions for two disjoint ranges
OSV record GHSA-vh95-rmgr-6w4m shows two separate semver timelines—one fixed at 0.2.1, the other fixed at 1.2.3: [2]
{
"affected": [
{
"package": { "ecosystem": "npm", "name": "minimist" },
"ranges": [
{ "type": "SEMVER", "events": [ { "introduced": "0" }, { "fixed": "0.2.1" } ] }
]
},
{
"package": { "ecosystem": "npm", "name": "minimist" },
"ranges": [
{ "type": "SEMVER", "events": [ { "introduced": "1.0.0" }, { "fixed": "1.2.3" } ] }
]
}
]
}Why this works (spec detail)
Each ranges[].events array is a timeline of status changes, where introduced starts affectedness and fixed ends it; OSV allows multiple such timelines to represent multiple vulnerable intervals. [1]
Sources
[1] OSV schema docs (ranges/events semantics): https://cvedb.github.io/osv-schema/
[2] OSV JSON for GHSA-vh95-rmgr-6w4m (npm/minimist): https://api.osv.dev/v1/vulns/GHSA-vh95-rmgr-6w4m
Implement deterministic selection of the earliest fixed version.
OSV schema allows multiple ranges with different fixed versions (e.g., separate maintenance branches). The current implementation returns the first fixed event encountered, which is non-deterministic and may return a later version than necessary. Per OSV specification, consumers should sort events by the range type's version precedence.
Proposed fix (collect & choose lowest SEMVER fix)
+import semver from 'semver'
import type {
OsvQueryResponse,
OsvBatchResponse,
OsvVulnerability,
OsvSeverityLevel,
VulnerabilitySummary,
DependencyDepth,
PackageVulnerabilityInfo,
VulnerabilityTreeResult,
DeprecatedPackageInfo,
OsvAffected,
} from '#shared/types/dependency-analysis'
function getFixedVersion(
affected: OsvAffected[] | undefined,
packageName: string,
): string | undefined {
if (!affected) return undefined
// Find the affected entry for this specific package
const packageAffected = affected.find(
a => a.package.ecosystem === 'npm' && a.package.name === packageName,
)
if (!packageAffected?.ranges) return undefined
- // Look through ranges to find a 'fixed' event
- for (const range of packageAffected.ranges) {
- for (const event of range.events) {
- if (event.fixed) {
- return event.fixed
- }
- }
- }
-
- return undefined
+ const fixedVersions = packageAffected.ranges
+ .filter(range => range.type === 'SEMVER' || range.type === 'ECOSYSTEM')
+ .flatMap(range => range.events.map(event => event.fixed).filter(Boolean) as string[])
+ .filter(version => semver.valid(version))
+
+ if (fixedVersions.length === 0) return undefined
+ return fixedVersions.sort(semver.compare)[0]
}
Fixes: #954
Get OSV-fixed version(s) for vulnerabilities if available and shoe it inside vulnerabilities banner.