VYPR
Medium severity5.3NVD Advisory· Published Sep 18, 2024· Updated Apr 15, 2026

CVE-2024-45813

CVE-2024-45813

Description

find-my-way is a fast, open source HTTP router, internally using a Radix Tree (aka compact Prefix Tree), supports route params, wildcards, and it's framework independent. A bad regular expression is generated any time one has two parameters within a single segment, when adding a - at the end, like /:a-:b-. This may cause a denial of service in some instances. Users are advised to update to find-my-way v8.2.2 or v9.0.1. or subsequent versions. There are no known workarounds for this issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
find-my-waynpm
>= 5.5.0, < 8.2.28.2.2
find-my-waynpm
>= 9.0.0, < 9.0.19.0.1

Patches

4
5e9e0eb5d8d4

Merge commit from fork

https://github.com/delvedor/find-my-wayBlake EmbreySep 17, 2024via ghsa
4 files changed · +31 8
  • index.js+13 4 modified
    @@ -192,6 +192,8 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
     
         if (isParametricNode) {
           let isRegexNode = false
    +      let isParamSafe = true
    +      let backtrack = ''
           const regexps = []
     
           let lastParamStartIndex = i + 1
    @@ -219,8 +221,10 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
                 regexps.push(trimRegExpStartAndEnd(regexString))
     
                 j = endOfRegexIndex + 1
    +            isParamSafe = true
               } else {
    -            regexps.push('(.*?)')
    +            regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`)
    +            isParamSafe = false
               }
     
               const staticPartStartIndex = j
    @@ -238,7 +242,7 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
               if (staticPart) {
                 staticPart = staticPart.split('::').join(':')
                 staticPart = staticPart.split('%').join('%25')
    -            regexps.push(escapeRegExp(staticPart))
    +            regexps.push(backtrack = escapeRegExp(staticPart))
               }
     
               lastParamStartIndex = j + 1
    @@ -335,6 +339,8 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
     
         if (isParametricNode) {
           let isRegexNode = false
    +      let isParamSafe = true
    +      let backtrack = ''
           const regexps = []
     
           let lastParamStartIndex = i + 1
    @@ -344,6 +350,7 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
             const isRegexParam = charCode === 40
             const isStaticPart = charCode === 45 || charCode === 46
             const isEndOfNode = charCode === 47 || j === pattern.length
    +
             if (isRegexParam || isStaticPart || isEndOfNode) {
               const paramName = pattern.slice(lastParamStartIndex, j)
               params.push(paramName)
    @@ -361,8 +368,10 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
                 regexps.push(trimRegExpStartAndEnd(regexString))
     
                 j = endOfRegexIndex + 1
    +            isParamSafe = false
               } else {
    -            regexps.push('(.*?)')
    +            regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`)
    +            isParamSafe = false
               }
     
               const staticPartStartIndex = j
    @@ -380,7 +389,7 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
               if (staticPart) {
                 staticPart = staticPart.split('::').join(':')
                 staticPart = staticPart.split('%').join('%25')
    -            regexps.push(escapeRegExp(staticPart))
    +            regexps.push(backtrack = escapeRegExp(staticPart))
               }
     
               lastParamStartIndex = j + 1
    
  • test/issue-17.test.js+2 2 modified
    @@ -132,8 +132,8 @@ test('Multi parametric route / 2', t => {
       })
     
       findMyWay.on('GET', '/a/:p1-:p2', (req, res, params) => {
    -    t.equal(params.p1, 'foo')
    -    t.equal(params.p2, 'bar-baz')
    +    t.equal(params.p1, 'foo-bar')
    +    t.equal(params.p2, 'baz')
       })
     
       findMyWay.on('GET', '/b/:p1.:p2', (req, res, params) => {
    
  • test/optional-params.test.js+2 2 modified
    @@ -68,8 +68,8 @@ test('Multi parametric route with optional param', (t) => {
     
       findMyWay.on('GET', '/a/:p1-:p2?', (req, res, params) => {
         if (params.p1 && params.p2) {
    -      t.equal(params.p1, 'foo')
    -      t.equal(params.p2, 'bar-baz')
    +      t.equal(params.p1, 'foo-bar')
    +      t.equal(params.p2, 'baz')
         }
       })
     
    
  • test/regex.test.js+14 0 modified
    @@ -255,3 +255,17 @@ test('Disable safe regex check', t => {
         }
       })
     })
    +
    +test('prevent back-tracking', (t) => {
    +  t.plan(0)
    +  t.setTimeout(20)
    +
    +  const findMyWay = FindMyWay({
    +    defaultRoute: () => {
    +      t.fail('route not matched')
    +    }
    +  })
    +
    +  findMyWay.on('GET', '/:foo-:bar-', (req, res, params) => {})
    +  findMyWay.find('GET', '/' + '-'.repeat(16_000) + 'a', { host: 'fastify.io' })
    +})
    
17fae694dcef

Merge commit from fork

https://github.com/delvedor/find-my-wayBlake EmbreySep 17, 2024via ghsa
4 files changed · +31 8
  • index.js+13 4 modified
    @@ -192,6 +192,8 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
     
         if (isParametricNode) {
           let isRegexNode = false
    +      let isParamSafe = true
    +      let backtrack = ''
           const regexps = []
     
           let lastParamStartIndex = i + 1
    @@ -219,8 +221,10 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
                 regexps.push(trimRegExpStartAndEnd(regexString))
     
                 j = endOfRegexIndex + 1
    +            isParamSafe = true
               } else {
    -            regexps.push('(.*?)')
    +            regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`)
    +            isParamSafe = false
               }
     
               const staticPartStartIndex = j
    @@ -238,7 +242,7 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
               if (staticPart) {
                 staticPart = staticPart.split('::').join(':')
                 staticPart = staticPart.split('%').join('%25')
    -            regexps.push(escapeRegExp(staticPart))
    +            regexps.push(backtrack = escapeRegExp(staticPart))
               }
     
               lastParamStartIndex = j + 1
    @@ -335,6 +339,8 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
     
         if (isParametricNode) {
           let isRegexNode = false
    +      let isParamSafe = true
    +      let backtrack = ''
           const regexps = []
     
           let lastParamStartIndex = i + 1
    @@ -344,6 +350,7 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
             const isRegexParam = charCode === 40
             const isStaticPart = charCode === 45 || charCode === 46
             const isEndOfNode = charCode === 47 || j === pattern.length
    +
             if (isRegexParam || isStaticPart || isEndOfNode) {
               const paramName = pattern.slice(lastParamStartIndex, j)
               params.push(paramName)
    @@ -361,8 +368,10 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
                 regexps.push(trimRegExpStartAndEnd(regexString))
     
                 j = endOfRegexIndex + 1
    +            isParamSafe = false
               } else {
    -            regexps.push('(.*?)')
    +            regexps.push(isParamSafe ? '(.*?)' : `(${backtrack}|(?:(?!${backtrack}).)*)`)
    +            isParamSafe = false
               }
     
               const staticPartStartIndex = j
    @@ -380,7 +389,7 @@ Router.prototype.findRoute = function findNode (method, path, constraints = {})
               if (staticPart) {
                 staticPart = staticPart.split('::').join(':')
                 staticPart = staticPart.split('%').join('%25')
    -            regexps.push(escapeRegExp(staticPart))
    +            regexps.push(backtrack = escapeRegExp(staticPart))
               }
     
               lastParamStartIndex = j + 1
    
  • test/issue-17.test.js+2 2 modified
    @@ -132,8 +132,8 @@ test('Multi parametric route / 2', t => {
       })
     
       findMyWay.on('GET', '/a/:p1-:p2', (req, res, params) => {
    -    t.equal(params.p1, 'foo')
    -    t.equal(params.p2, 'bar-baz')
    +    t.equal(params.p1, 'foo-bar')
    +    t.equal(params.p2, 'baz')
       })
     
       findMyWay.on('GET', '/b/:p1.:p2', (req, res, params) => {
    
  • test/optional-params.test.js+2 2 modified
    @@ -68,8 +68,8 @@ test('Multi parametric route with optional param', (t) => {
     
       findMyWay.on('GET', '/a/:p1-:p2?', (req, res, params) => {
         if (params.p1 && params.p2) {
    -      t.equal(params.p1, 'foo')
    -      t.equal(params.p2, 'bar-baz')
    +      t.equal(params.p1, 'foo-bar')
    +      t.equal(params.p2, 'baz')
         }
       })
     
    
  • test/regex.test.js+14 0 modified
    @@ -255,3 +255,17 @@ test('Disable safe regex check', t => {
         }
       })
     })
    +
    +test('prevent back-tracking', (t) => {
    +  t.plan(0)
    +  t.setTimeout(20)
    +
    +  const findMyWay = FindMyWay({
    +    defaultRoute: () => {
    +      t.fail('route not matched')
    +    }
    +  })
    +
    +  findMyWay.on('GET', '/:foo-:bar-', (req, res, params) => {})
    +  findMyWay.find('GET', '/' + '-'.repeat(16_000) + 'a', { host: 'fastify.io' })
    +})
    
66fa03923355

Define insert method for each node type (#248)

https://github.com/delvedor/find-my-wayIvan TimoshenkoApr 15, 2022via ghsa
3 files changed · +100 109
  • custom_node.js+71 49 modified
    @@ -34,37 +34,6 @@ Object.defineProperty(Node.prototype, 'types', {
       value: types
     })
     
    -Node.prototype.addChild = function (node) {
    -  const label = node.prefix[0]
    -  switch (node.kind) {
    -    case this.types.STATIC:
    -      assert(
    -        this.staticChildren[label] === undefined,
    -        `There is already a child with label '${label}'`
    -      )
    -      this.staticChildren[label] = node
    -      break
    -    case this.types.PARAM:
    -    case this.types.REGEX:
    -      assert(this.parametricChild === null, 'There is already a parametric child')
    -      this.parametricChild = node
    -      break
    -    case this.types.MATCH_ALL:
    -      assert(this.wildcardChild === null, 'There is already a wildcard child')
    -      this.wildcardChild = node
    -      break
    -    default:
    -      throw new Error(`Unknown node kind: ${node.kind}`)
    -  }
    -
    -  this.numberOfChildren++
    -
    -  this._saveParametricBrother()
    -  this._saveWildcardBrother()
    -
    -  return this
    -}
    -
     Node.prototype._saveParametricBrother = function () {
       let parametricBrother = this.parametricBrother
       if (this.parametricChild !== null) {
    @@ -76,7 +45,7 @@ Node.prototype._saveParametricBrother = function () {
       if (parametricBrother) {
         for (const child of Object.values(this.staticChildren)) {
           child.parametricBrother = parametricBrother
    -      child._saveParametricBrother(parametricBrother)
    +      child._saveParametricBrother()
         }
       }
     }
    @@ -92,7 +61,7 @@ Node.prototype._saveWildcardBrother = function () {
       if (wildcardBrother) {
         for (const child of Object.values(this.staticChildren)) {
           child.wildcardBrother = wildcardBrother
    -      child._saveWildcardBrother(wildcardBrother)
    +      child._saveWildcardBrother()
         }
         if (this.parametricChild !== null) {
           this.parametricChild.wildcardBrother = wildcardBrother
    @@ -116,7 +85,7 @@ Node.prototype.reset = function (prefix) {
     }
     
     Node.prototype.split = function (length) {
    -  const newChild = new Node(
    +  const staticChild = new Node(
         {
           prefix: this.prefix.slice(length),
           staticChildren: this.staticChildren,
    @@ -131,32 +100,85 @@ Node.prototype.split = function (length) {
       )
     
       if (this.wildcardChild !== null) {
    -    newChild.wildcardChild = this.wildcardChild
    +    staticChild.wildcardChild = this.wildcardChild
       }
     
       if (this.parametricChild !== null) {
    -    newChild.parametricChild = this.parametricChild
    +    staticChild.parametricChild = this.parametricChild
       }
     
       this.reset(this.prefix.slice(0, length))
    -  this.addChild(newChild)
    -  return newChild
    +
    +  const label = staticChild.prefix.charAt(0)
    +  this.staticChildren[label] = staticChild
    +
    +  this.numberOfChildren++
    +
    +  this._saveParametricBrother()
    +  this._saveWildcardBrother()
    +
    +  return staticChild
     }
     
    -Node.prototype.getChildByLabel = function (label, kind) {
    -  if (label.length === 0) {
    -    return null
    +Node.prototype.insertStaticNode = function (path) {
    +  if (path.length === 0) {
    +    return this
       }
     
    -  switch (kind) {
    -    case this.types.STATIC:
    -      return this.staticChildren[label]
    -    case this.types.MATCH_ALL:
    -      return this.wildcardChild
    -    case this.types.PARAM:
    -    case this.types.REGEX:
    -      return this.parametricChild
    +  let staticChild = this.staticChildren[path.charAt(0)]
    +  if (staticChild) {
    +    let i = 0
    +    for (; i < staticChild.prefix.length; i++) {
    +      if (path.charCodeAt(i) !== staticChild.prefix.charCodeAt(i)) {
    +        staticChild.split(i)
    +        break
    +      }
    +    }
    +    return staticChild.insertStaticNode(path.slice(i))
       }
    +
    +  staticChild = new Node({ method: this.method, prefix: path, kind: types.STATIC, constrainer: this.constrainer })
    +
    +  const label = path.charAt(0)
    +  this.staticChildren[label] = staticChild
    +  this.numberOfChildren++
    +
    +  this._saveParametricBrother()
    +  this._saveWildcardBrother()
    +
    +  return staticChild
    +}
    +
    +Node.prototype.insertParametricNode = function (regex) {
    +  if (this.parametricChild) {
    +    return this.parametricChild
    +  }
    +
    +  const kind = regex ? types.REGEX : types.PARAM
    +  const parametricChild = new Node({ method: this.method, prefix: ':', kind, regex, constrainer: this.constrainer })
    +
    +  this.parametricChild = parametricChild
    +  this.numberOfChildren++
    +
    +  this._saveParametricBrother()
    +  this._saveWildcardBrother()
    +
    +  return parametricChild
    +}
    +
    +Node.prototype.insertWildcardNode = function () {
    +  if (this.wildcardChild) {
    +    return this.wildcardChild
    +  }
    +
    +  const wildcardChild = new Node({ method: this.method, prefix: '*', kind: types.MATCH_ALL, constrainer: this.constrainer })
    +
    +  this.wildcardChild = wildcardChild
    +  this.numberOfChildren++
    +
    +  this._saveWildcardBrother()
    +
    +  return wildcardChild
     }
     
     Node.prototype.findStaticMatchingChild = function (path, pathIndex) {
    
  • index.js+26 54 modified
    @@ -166,29 +166,34 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
     
       const params = []
       for (let i = 0; i <= path.length; i++) {
    -    // search for parametric or wildcard routes
    -    // parametric route
    -    if (path.charCodeAt(i) === 58) {
    -      if (path.charCodeAt(i + 1) === 58) {
    -        // It's a double colon. Let's just replace it with a single colon and go ahead
    -        path = path.slice(0, i) + path.slice(i + 1)
    -        continue
    -      }
    +    if (path.charCodeAt(i) === 58 && path.charCodeAt(i + 1) === 58) {
    +      // It's a double colon. Let's just replace it with a single colon and go ahead
    +      path = path.slice(0, i) + path.slice(i + 1)
    +      continue
    +    }
     
    -      // add the static part of the route to the tree
    -      currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex, i), NODE_TYPES.STATIC, null)
    +    const isParametricNode = path.charCodeAt(i) === 58
    +    const isWildcardNode = path.charCodeAt(i) === 42
     
    -      const paramStartIndex = i + 1
    +    if (isParametricNode || isWildcardNode || (i === path.length && i !== parentNodePathIndex)) {
    +      let staticNodePath = path.slice(parentNodePathIndex, i)
    +      if (!this.caseSensitive) {
    +        staticNodePath = staticNodePath.toLowerCase()
    +      }
    +      // add the static part of the route to the tree
    +      currentNode = currentNode.insertStaticNode(staticNodePath)
    +    }
     
    +    if (isParametricNode) {
    +      let isRegexNode = false
           const regexps = []
    -      let nodeType = NODE_TYPES.PARAM
    -      let lastParamStartIndex = paramStartIndex
     
    -      for (let j = paramStartIndex; ; j++) {
    +      let lastParamStartIndex = i + 1
    +      for (let j = lastParamStartIndex; ; j++) {
             const charCode = path.charCodeAt(j)
     
             if (charCode === 40 || charCode === 45 || charCode === 46) {
    -          nodeType = NODE_TYPES.REGEX
    +          isRegexNode = true
     
               const paramName = path.slice(lastParamStartIndex, j)
               params.push(paramName)
    @@ -233,63 +238,30 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
             }
     
             if (path.charCodeAt(j) === 47 || j === path.length) {
    -          path = path.slice(0, paramStartIndex) + path.slice(j)
    +          path = path.slice(0, i + 1) + path.slice(j)
               break
             }
           }
     
           let regex = null
    -      if (nodeType === NODE_TYPES.REGEX) {
    +      if (isRegexNode) {
             regex = new RegExp('^' + regexps.join('') + '$')
           }
     
    -      currentNode = this._insert(currentNode, method, ':', nodeType, regex)
    +      currentNode = currentNode.insertParametricNode(regex)
           parentNodePathIndex = i + 1
    -    // wildcard route
    -    } else if (path.charCodeAt(i) === 42) {
    -      currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex, i), NODE_TYPES.STATIC, null)
    +    } else if (isWildcardNode) {
           // add the wildcard parameter
           params.push('*')
    -      currentNode = this._insert(currentNode, method, path.slice(i), NODE_TYPES.MATCH_ALL, null)
    -      break
    -    } else if (i === path.length && i !== parentNodePathIndex) {
    -      currentNode = this._insert(currentNode, method, path.slice(parentNodePathIndex), NODE_TYPES.STATIC, null)
    +      currentNode = currentNode.insertWildcardNode()
    +      parentNodePathIndex = i + 1
         }
       }
     
       assert(!currentNode.getHandler(constraints), `Method '${method}' already declared for route '${path}' with constraints '${JSON.stringify(constraints)}'`)
       currentNode.addHandler(handler, params, store, constraints)
     }
     
    -Router.prototype._insert = function _insert (currentNode, method, path, kind, regex) {
    -  if (!this.caseSensitive) {
    -    path = path.toLowerCase()
    -  }
    -
    -  let childNode = currentNode.getChildByLabel(path.charAt(0), kind)
    -  while (childNode) {
    -    currentNode = childNode
    -
    -    let i = 0
    -    for (; i < currentNode.prefix.length; i++) {
    -      if (path.charCodeAt(i) !== currentNode.prefix.charCodeAt(i)) {
    -        currentNode.split(i)
    -        break
    -      }
    -    }
    -    path = path.slice(i)
    -    childNode = currentNode.getChildByLabel(path.charAt(0), kind)
    -  }
    -
    -  if (path.length > 0) {
    -    const node = new Node({ method, prefix: path, kind, handlers: null, regex, constrainer: this.constrainer })
    -    currentNode.addChild(node)
    -    currentNode = node
    -  }
    -
    -  return currentNode
    -}
    -
     Router.prototype.reset = function reset () {
       this.trees = {}
       this.routes = []
    
  • test/issue-104.test.js+3 6 modified
    @@ -210,17 +210,14 @@ test('Mixed parametric routes, with last defined route being static', t => {
     test('parametricBrother of Parent Node, with a parametric child', t => {
       t.plan(1)
       const parent = new Node({ prefix: '/a' })
    -  const parametricChild = new Node({ prefix: ':id', kind: parent.types.PARAM })
    -  parent.addChild(parametricChild)
    +  parent.insertParametricNode()
       t.equal(parent.parametricBrother, null)
     })
     
     test('parametricBrother of Parent Node, with a parametric child and a static child', t => {
       t.plan(1)
       const parent = new Node({ prefix: '/a' })
    -  const parametricChild = new Node({ prefix: ':id', kind: parent.types.PARAM })
    -  const staticChild = new Node({ prefix: '/b', kind: parent.types.STATIC })
    -  parent.addChild(parametricChild)
    -  parent.addChild(staticChild)
    +  parent.insertParametricNode()
    +  parent.insertStaticNode('/b')
       t.equal(parent.parametricBrother, null)
     })
    

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

8

News mentions

0

No linked articles in our index yet.