[Bug] satisfiesRange ignores the 0.x caret rule, so ^0.25.0 admits 0.26.0 and up

Open Beginner friendly
#1,177 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, typescript
Domain
security, tooling

Research direction

Read the caret branch at src/utils/version.ts:223 and compare it with isBreakingUpgrade() in the same file. Add 0.x cases to tests/utils/version-extensions.test.ts, covering 0.y.z and 0.0.z behavior, then run the version tests. Done means the listed cases match npm semantics without changing the existing ~ or nonzero-major behavior.

Written by the indexing model from the issue text.

Description

bug

Summary

satisfiesRange() treats ^ as "same major, >= floor". That's right for ^1.2.0 but wrong for 0.x: npm resolves ^0.25.0 to >=0.25.0 <0.26.0, because with a zero major the minor is the breaking position. The caret branch only compares majors, so 0 === 0 matches every 0.y.z above the floor.

src/utils/version.ts:223:

if (trimmedRange.startsWith("^")) {
  ...
  return vMajor === tMajor;   // true for every 0.x
}

isBreakingUpgrade() in the same file already handles the 0.x rule (from #990). satisfiesRange() never got it. The ~ branch is fine, it compares major and minor.

Actual vs expected

satisfiesRange("0.26.0", "^0.25.0") -> true    npm: false
satisfiesRange("0.99.0", "^0.25.0") -> true    npm: false
satisfiesRange("0.3.0",  "^0.2.3")  -> true    npm: false
satisfiesRange("0.0.4",  "^0.0.3")  -> true    npm: false
satisfiesRange("0.25.1", "^0.25.0") -> true    npm: true   (fine)

Why it matters

satisfiesRange is the semver oracle for four OA rules, and caret pins on 0.x are common in overrides (esbuild and its @esbuild/* platform binaries, vite plugins, most of the current toolchain).

The one that bites hardest is OA010. admitted-scan.ts:73 builds the admitted set with published.filter(v => satisfiesRange(v, range)), so for "esbuild": "^0.25.0" it admits every published 0.26.x, 0.27.x, ... and then reports "this floor admits vulnerable esbuild@0.2X.Y" for versions npm can never resolve under that override. False positive on a rule whose whole job is judging a floor.

Same wrong answer reaches OA006 (oa006:144, "installed copy is consistent with the pin"), OA005 (oa005:165) and OA008 (oa008:38), in the false-negative direction.

Reproduction

import { satisfiesRange } from "./src/utils/version.js";
satisfiesRange("0.26.0", "^0.25.0");  // true, npm says false

Fix

Take the ceiling from the leftmost non-zero component of the target: major > 0 -> same major; 0.y.z with y > 0 -> same major and minor; 0.0.z -> exact match.

tests/utils/version-extensions.test.ts only covers ^1.2.0, which is why this went unnoticed. Worth a 0.x case per branch.

Environment

Node v26.7.0, macOS, npm, cve-lite-cli 1.35.0 (main @ dad43cd). Pure version logic, no lockfile involved.


Separate but adjacent: the same function's doc comment promises pre-release exclusion it doesn't implement (coerceVersion strips the tag, so satisfiesRange("1.0.0-beta", "^1.0.0") is true; npm says false). Different branch, different fix. Say the word and I'll file it on its own.

Happy to pick this up if you want a PR.

Dominant language
TypeScript
Stars
720
Forks
147
Avg merge
16h 48m
Merged PRs (30d)
68

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from OWASP/cve-lite-cli

All issues in OWASP/cve-lite-cli

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.