Skip to content

feat: show fixed version for vulnerabilities#967

Open
Flo0806 wants to merge 1 commit intonpmx-dev:mainfrom
Flo0806:feat/fixed-vulns-versions
Open

feat: show fixed version for vulnerabilities#967
Flo0806 wants to merge 1 commit intonpmx-dev:mainfrom
Flo0806:feat/fixed-vulns-versions

Conversation

@Flo0806
Copy link
Collaborator

@Flo0806 Flo0806 commented Feb 4, 2026

Fixes: #954


Get OSV-fixed version(s) for vulnerabilities if available and shoe it inside vulnerabilities banner.

@vercel
Copy link

vercel bot commented Feb 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 4, 2026 9:19pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 4, 2026 9:19pm
npmx-lunaria Ignored Ignored Feb 4, 2026 9:19pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/ar-EG.json Localization changed, will be marked as complete.
lunaria/files/cs-CZ.json Localization changed, will be marked as complete.
lunaria/files/de-DE.json Localization changed, will be marked as complete.
lunaria/files/en-GB.json Localization changed, will be marked as complete.
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/es-419.json Localization changed, will be marked as complete.
lunaria/files/es-ES.json Localization changed, will be marked as complete.
lunaria/files/fr-FR.json Localization changed, will be marked as complete.
lunaria/files/hi-IN.json Localization changed, will be marked as complete.
lunaria/files/hu-HU.json Localization changed, will be marked as complete.
lunaria/files/id-ID.json Localization changed, will be marked as complete.
lunaria/files/it-IT.json Localization changed, will be marked as complete.
lunaria/files/ja-JP.json Localization changed, will be marked as complete.
lunaria/files/ne-NP.json Localization changed, will be marked as complete.
lunaria/files/pl-PL.json Localization changed, will be marked as complete.
lunaria/files/pt-BR.json Localization changed, will be marked as complete.
lunaria/files/ru-RU.json Localization changed, will be marked as complete.
lunaria/files/uk-UA.json Localization changed, will be marked as complete.
lunaria/files/zh-CN.json Localization changed, will be marked as complete.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@codecov
Copy link

codecov bot commented Feb 4, 2026

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/Package/VulnerabilityTree.vue 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

The 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 fixed_in_title translation key across supported languages, backend utility changes to extract the earliest fixed version from OSV vulnerability data, and TypeScript type definitions expanded to support affected version ranges and a fixedIn field in vulnerability summaries.

Possibly related PRs

  • PR #869: Updates i18n/locales/es.json with vulnerability localisation keys

Suggested reviewers

  • danielroe
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly references issue #954 and describes the implementation goal of retrieving and displaying OSV-fixed versions for vulnerabilities in the UI.
Linked Issues check ✅ Passed The pull request successfully implements all coding requirements from issue #954: extracting fixed versions from OSV data and displaying them in the vulnerability banner alongside vulnerability information.
Out of Scope Changes check ✅ Passed All changes are focused on implementing the fixed-version feature: backend OSV data extraction, type definitions, translation strings, and UI display. No unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Type definitions do not enforce OSV schema constraints; strict type-safety required.

The OsvRangeEvent interface allows zero or multiple fields simultaneously, but the official OSV schema mandates exactly one of: introduced, fixed, last_affected, or limit. Additionally, OsvRange is missing the required repo field (mandatory when type is 'GIT'), and the events array constraint—at least one introduced event 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.

Comment on lines +149 to +175
/**
* 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
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 events sorted according to the ordering defined by affected[].ranges[].type (“While not required, it’s also recommended to keep the events array sorted according to the affected[].ranges[].type of 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 tsx

Repository: 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 -5

Repository: 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): returns 0 if equal, 1 if v1 is greater, -1 if v2 is greater; sorts ascending when used as an Array.sort() comparator. [1][2]

Return value semantics

  • 0v1 and v2 are semver-equal
  • 1v1 > v2
  • -1v1 < v2 (i.e., v2 > v1) [1][2]

Options / strictness

  • Functions accept an optional final options object (or historically a boolean interpreted as { loose: true }), where loose enables 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 -10

Repository: 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]
 }

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: show fixed versions for vulnerabilities

1 participant