VYPR
High severity7.5NVD Advisory· Published Jan 23, 2017· Updated May 13, 2026

CVE-2015-8855

CVE-2015-8855

Description

The semver package before 4.3.2 for Node.js allows attackers to cause a denial of service (CPU consumption) via a long version string, aka a "regular expression denial of service (ReDoS)."

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
semvernpm
>= 1.0.4, < 4.3.24.3.2

Patches

2
c80180d8341a

Prevent version strings > 256 chars, or with giant numbers

https://github.com/npm/node-semverisaacsMar 27, 2015via ghsa
2 files changed · +50 1
  • semver.js+26 1 modified
    @@ -20,6 +20,9 @@ if (typeof module === 'object' && module.exports === exports)
     // Not necessarily the package version of this code.
     exports.SEMVER_SPEC_VERSION = '2.0.0';
     
    +var MAX_LENGTH = 256;
    +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991;
    +
     // The actual regexps go on exports.re
     var re = exports.re = [];
     var src = exports.src = [];
    @@ -233,8 +236,18 @@ for (var i = 0; i < R; i++) {
     
     exports.parse = parse;
     function parse(version, loose) {
    +  if (version.length > MAX_LENGTH)
    +    return null;
    +
       var r = loose ? re[LOOSE] : re[FULL];
    -  return (r.test(version)) ? new SemVer(version, loose) : null;
    +  if (!r.test(version))
    +    return null;
    +
    +  try {
    +    return new SemVer(version, loose);
    +  } catch (er) {
    +    return null;
    +  }
     }
     
     exports.valid = valid;
    @@ -262,6 +275,9 @@ function SemVer(version, loose) {
         throw new TypeError('Invalid Version: ' + version);
       }
     
    +  if (version.length > MAX_LENGTH)
    +    throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters')
    +
       if (!(this instanceof SemVer))
         return new SemVer(version, loose);
     
    @@ -279,6 +295,15 @@ function SemVer(version, loose) {
       this.minor = +m[2];
       this.patch = +m[3];
     
    +  if (this.major > MAX_SAFE_INTEGER || this.major < 0)
    +    throw new TypeError('Invalid major version')
    +
    +  if (this.minor > MAX_SAFE_INTEGER || this.minor < 0)
    +    throw new TypeError('Invalid minor version')
    +
    +  if (this.patch > MAX_SAFE_INTEGER || this.patch < 0)
    +    throw new TypeError('Invalid patch version')
    +
       // numberify any prerelease numeric ids
       if (!m[4])
         this.prerelease = [];
    
  • test/big-numbers.js+24 0 added
    @@ -0,0 +1,24 @@
    +var test = require('tap').test
    +var semver = require('../')
    +
    +test('long version is too long', function (t) {
    +  var v = '1.2.' + new Array(256).join('1')
    +  t.throws(function () {
    +    new semver.SemVer(v)
    +  })
    +  t.equal(semver.valid(v, false), null)
    +  t.equal(semver.valid(v, true), null)
    +  t.equal(semver.inc(v, 'patch'), null)
    +  t.end()
    +})
    +
    +test('big number is like too long version', function (t) {
    +  var v = '1.2.' + new Array(100).join('1')
    +  t.throws(function () {
    +    new semver.SemVer(v)
    +  })
    +  t.equal(semver.valid(v, false), null)
    +  t.equal(semver.valid(v, true), null)
    +  t.equal(semver.inc(v, 'patch'), null)
    +  t.end()
    +})
    
5c4c9f6e26c7

Close #2 Also, docs.

https://github.com/npm/node-semverisaacsApr 21, 2011via ghsa
2 files changed · +128 7
  • README.md+32 0 modified
    @@ -84,3 +84,35 @@ The following range styles are supported:
     
     Ranges can be joined with either a space (which implies "and") or a
     `||` (which implies "or").
    +
    +## Functions
    +
    +* valid(v): Return the parsed version, or null if it's not valid.
    +
    +### Comparison
    +
    +* gt(v1, v2): `v1 > v2`
    +* gte(v1, v2): `v1 >= v2`
    +* lt(v1, v2): `v1 < v2`
    +* lte(v1, v2): `v1 <= v2`
    +* eq(v1, v2): `v1 == v2` This is true if they're logically equivalent,
    +  even if they're not the exact same string.  You already know how to
    +  compare strings.
    +* neq(v1, v2): `v1 != v2` The opposite of eq.
    +* cmp(v1, comparator, v2): Pass in a comparison string, and it'll call
    +  the corresponding function above.  `"==="` and `"!=="` do simple
    +  string comparison, but are included for completeness.  Throws if an
    +  invalid comparison string is provided.
    +* compare(v1, v2): Return 0 if v1 == v2, or 1 if v1 is greater, or -1 if
    +  v2 is greater.  Sorts in ascending order if passed to Array.sort().
    +* rcompare(v1, v2): The reverse of compare.  Sorts an array of versions
    +  in descending order when passed to Array.sort().
    +
    +
    +### Ranges
    +
    +* validRange(range): Return the valid range or null if it's not valid
    +* satisfies(version, range): Return true if the version satisfies the
    +  range.
    +* maxSatisfying(versions, range): Return the highest version in the list
    +  that satisfies the range, or null if none of them do.
    
  • semver.js+96 7 modified
    @@ -3,7 +3,7 @@
     // This implementation is a *hair* less strict in that it allows
     // v1.2.3 things, and also tags that don't begin with a char.
     
    -var semver = "[v=]*([0-9]+)"                // major
    +var semver = "\\s*[v=]*\\s*([0-9]+)"                // major
                + "\\.([0-9]+)"                  // minor
                + "\\.([0-9]+)"                  // patch
                + "(-[0-9]+-?)?"                 // build
    @@ -30,7 +30,13 @@ exports.clean = clean
     exports.compare = compare
     exports.satisfies = satisfies
     exports.gt = gt
    +exports.gte = gte
     exports.lt = lt
    +exports.lte = lte
    +exports.eq = eq
    +exports.neq = neq
    +exports.cmp = cmp
    +
     exports.valid = valid
     exports.validPackage = validPackage
     exports.validRange = validRange
    @@ -41,9 +47,11 @@ function clean (ver) {
       if (!v) return v
       return [v[1]||'', v[2]||'', v[3]||''].join(".") + (v[4]||'') + (v[5]||'')
     }
    +
     function valid (version) {
       return exports.parse(version) && version.trim().replace(/^[v=]+/, '')
     }
    +
     function validPackage (version) {
       return version.match(expressions.parsePackage) && version.trim()
     }
    @@ -97,6 +105,7 @@ function replaceXRanges (ranges) {
                    .map(replaceXRange)
                    .join(" ")
     }
    +
     function replaceXRange (version) {
       return version.trim().replace(expressions.parseXRange,
                                     function (v, gtlt, M, m, p) {
    @@ -191,10 +200,32 @@ function satisfies (version, range) {
     
     // return v1 > v2 ? 1 : -1
     function compare (v1, v2) {
    -  return v1 === v2 ? 0 : gt(v1, v2) ? 1 : -1
    +  var g = gt(v1, v2)
    +  return g === null ? 0 : g ? 1 : -1
    +}
    +
    +function rcompare (v1, v2) {
    +  return compare(v2, v1)
     }
     
     function lt (v1, v2) { return gt(v2, v1) }
    +function gte (v1, v2) { return !lt(v1, v2) }
    +function lte (v1, v2) { return !gt(v1, v2) }
    +function eq (v1, v2) { return gt(v1, v2) === null }
    +function neq (v1, v2) { return gt(v1, v2) !== null }
    +function cmp (v1, c, v2) {
    +  switch (c) {
    +    case ">": return gt(v1, v2)
    +    case "<": return lt(v1, v2)
    +    case ">=": return gte(v1, v2)
    +    case "<=": return lte(v1, v2)
    +    case "==": return eq(v1, v2)
    +    case "!=": return neq(v1, v2)
    +    case "===": return v1 === v2
    +    case "!==": return v1 !== v2
    +    default: throw new Error("Y U NO USE VALID COMPARATOR!? "+c)
    +  }
    +}
     
     // return v1 > v2
     function num (v) {
    @@ -214,7 +245,13 @@ function gt (v1, v2) {
       // no tag is > than any tag, or use lexicographical order.
       var tag1 = v1[5] || ""
         , tag2 = v2[5] || ""
    -  return !!tag2 && (!tag1 || tag1 > tag2)
    +
    +  // kludge: null means they were equal.  falsey, and detectable.
    +  // embarrassingly overclever, though, I know.
    +  return tag1 === tag2 ? null
    +         : !tag1 ? true
    +         : !tag2 ? false
    +         : tag1 > tag2
     }
     
     if (module === require.main) {  // tests below
    @@ -243,11 +280,63 @@ var assert = require("assert")
       , ["1.2.3-4-foo", "1.2.3"]
       , ["1.2.3-5", "1.2.3-5-foo"]
       , ["1.2.3-5", "1.2.3-4"]
    +  , ["1.2.3-5-foo", "1.2.3-5-Foo"]
    +  ].forEach(function (v) {
    +    var v0 = v[0]
    +      , v1 = v[1]
    +    assert.ok(gt(v0, v1), "gt('"+v0+"', '"+v1+"')")
    +    assert.ok(lt(v1, v0), "lt('"+v1+"', '"+v0+"')")
    +    assert.ok(!gt(v1, v0), "!gt('"+v1+"', '"+v0+"')")
    +    assert.ok(!lt(v0, v1), "!lt('"+v0+"', '"+v1+"')")
    +    assert.ok(eq(v0, v0), "eq('"+v0+"', '"+v0+"')")
    +    assert.ok(eq(v1, v1), "eq('"+v1+"', '"+v1+"')")
    +    assert.ok(neq(v0, v1), "neq('"+v0+"', '"+v1+"')")
    +    assert.ok(cmp(v1, "==", v1), "cmp("+v1+"=="+v1+")")
    +    assert.ok(cmp(v0, ">=", v1), "cmp("+v0+"<="+v1+")")
    +    assert.ok(cmp(v1, "<=", v0), "cmp("+v1+">="+v0+")")
    +    assert.ok(cmp(v0, "!=", v1), "cmp("+v0+"!="+v1+")")
    +  })
    +
    +// equality tests
    +; [ ["1.2.3", "v1.2.3"]
    +  , ["1.2.3", "=1.2.3"]
    +  , ["1.2.3", "v 1.2.3"]
    +  , ["1.2.3", "= 1.2.3"]
    +  , ["1.2.3", " v1.2.3"]
    +  , ["1.2.3", " =1.2.3"]
    +  , ["1.2.3", " v 1.2.3"]
    +  , ["1.2.3", " = 1.2.3"]
    +  , ["1.2.3-0", "v1.2.3-0"]
    +  , ["1.2.3-0", "=1.2.3-0"]
    +  , ["1.2.3-0", "v 1.2.3-0"]
    +  , ["1.2.3-0", "= 1.2.3-0"]
    +  , ["1.2.3-0", " v1.2.3-0"]
    +  , ["1.2.3-0", " =1.2.3-0"]
    +  , ["1.2.3-0", " v 1.2.3-0"]
    +  , ["1.2.3-0", " = 1.2.3-0"]
    +  , ["1.2.3-01", "v1.2.3-1"]
    +  , ["1.2.3-01", "=1.2.3-1"]
    +  , ["1.2.3-01", "v 1.2.3-1"]
    +  , ["1.2.3-01", "= 1.2.3-1"]
    +  , ["1.2.3-01", " v1.2.3-1"]
    +  , ["1.2.3-01", " =1.2.3-1"]
    +  , ["1.2.3-01", " v 1.2.3-1"]
    +  , ["1.2.3-01", " = 1.2.3-1"]
    +  , ["1.2.3beta", "v1.2.3beta"]
    +  , ["1.2.3beta", "=1.2.3beta"]
    +  , ["1.2.3beta", "v 1.2.3beta"]
    +  , ["1.2.3beta", "= 1.2.3beta"]
    +  , ["1.2.3beta", " v1.2.3beta"]
    +  , ["1.2.3beta", " =1.2.3beta"]
    +  , ["1.2.3beta", " v 1.2.3beta"]
    +  , ["1.2.3beta", " = 1.2.3beta"]
       ].forEach(function (v) {
    -    assert.ok(gt(v[0], v[1]), "gt('"+v[0]+"', '"+v[1]+"')")
    -    assert.ok(lt(v[1], v[0]), "lt('"+v[1]+"', '"+v[0]+"')")
    -    assert.ok(!gt(v[1], v[0]), "!gt('"+v[1]+"', '"+v[0]+"')")
    -    assert.ok(!lt(v[0], v[1]), "!lt('"+v[0]+"', '"+v[1]+"')")
    +    var v0 = v[0]
    +      , v1 = v[1]
    +    assert.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
    +    assert.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
    +    assert.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
    +    assert.ok(eq(v0, v1), "eq('"+v0+"', '"+v1+"')")
       })
     
     
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.