CVE-2020-7764: Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') (CWE-444)
value: This affects the package find-my-way before 2.2.5, from 3.0.0 and before 3.0.5. It accepts the Accept-Version' header by default, and if versioned routes are not being used, this could lead to a denial of service. Accept-Version can be used as an unkeyed header in a cache poisoning attack.
CWE description: The product acts as an intermediary HTTP agent
         (such as a proxy or firewall) in the data flow between two
         entities such as a client and server, but it does not
         interpret malformed HTTP requests or responses in ways that
         are consistent with how the messages will be processed by
         those entities that are at the ultimate destination.
CVSS v3 base score: 7.5
Published: 2020-11-08T16:15Z
Repository: https://github.com/delvedor/find-my-way
Language: JavaScript
Files changed: README.md, index.js, lib/accept-version.js, test/version.default-versioning.test.js
Commit date: 2020-11-06 18:30:04 +0100
Commit message: Enable versioning on demand (#172)


Diff: diff --git a/README.md b/README.md
index 209771b..d9c7964 100644
--- a/README.md
+++ b/README.md
@@ -142,11 +142,13 @@ router.on('GET', '/example', (req, res, params, store) => {
 }, { message: 'hello world' })
 ```
 
 ##### Versioned routes
 
-If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route.
+If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route. If you never configure a versioned route, the `'Accept-Version'` header will be ignored.
+
+Remember to set a [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) header in your responses with the value you are using for deifning the versioning (e.g.: 'Accept-Version'), to prevent cache poisoning attacks. You can also configure this as part your Proxy/CDN.
 
 ###### default
 <a name="semver"></a>
 Default versioning strategy is called `accept-version` and it follows the [semver](https://semver.org/) specification.<br/>
 When using `lookup`, `find-my-way` will automatically detect the `Accept-Version` header and route the request accordingly.<br/>
diff --git a/index.js b/index.js
index 6941dc9..9d3f99f 100644
--- a/index.js
+++ b/index.js
@@ -49,11 +49,11 @@ function Router (opts) {
 
   this.caseSensitive = opts.caseSensitive === undefined ? true : opts.caseSensitive
   this.ignoreTrailingSlash = opts.ignoreTrailingSlash || false
   this.maxParamLength = opts.maxParamLength || 100
   this.allowUnsafeRegex = opts.allowUnsafeRegex || false
-  this.versioning = opts.versioning || acceptVersionStrategy
+  this.versioning = opts.versioning || acceptVersionStrategy(false)
   this.trees = {}
   this.routes = []
 }
 
 Router.prototype.on = function on (method, path, opts, handler, store) {
@@ -109,10 +109,13 @@ Router.prototype._on = function _on (method, path, opts, handler, store) {
     handler: handler,
     store: store
   })
 
   const version = opts.version
+  if (version != null && this.versioning.disabled) {
+    this.versioning = acceptVersionStrategy(true)
+  }
 
   for (var i = 0, len = path.length; i < len; i++) {
     // search for parametric or wildcard routes
     // parametric route
     if (path.charCodeAt(i) === 58) {
diff --git a/lib/accept-version.js b/lib/accept-version.js
index 3d698c6..a5a7708 100644
--- a/lib/accept-version.js
+++ b/lib/accept-version.js
@@ -1,10 +1,21 @@
 'use strict'
 
 const SemVerStore = require('semver-store')
 
-module.exports = {
-  storage: SemVerStore,
-  deriveVersion: function (req, ctx) {
-    return req.headers['accept-version']
+function build (enabled) {
+  if (enabled) {
+    return {
+      storage: SemVerStore,
+      deriveVersion: function (req, ctx) {
+        return req.headers['accept-version']
+      }
+    }
+  }
+  return {
+    storage: SemVerStore,
+    deriveVersion: function (req, ctx) {},
+    disabled: true
   }
 }
+
+module.exports = build
diff --git a/test/version.default-versioning.test.js b/test/version.default-versioning.test.js
index 497f641..b6d42dd 100644
--- a/test/version.default-versioning.test.js
+++ b/test/version.default-versioning.test.js
@@ -238,5 +238,30 @@ test('It should throw if you declare multiple times the same route', t => {
     t.fail('It should throw')
   } catch (err) {
     t.is(err.message, 'Method \'GET\' already declared for route \'/\' version \'1.2.3\'')
   }
 })
+
+test('Versioning won\'t work if there are no versioned routes', t => {
+  t.plan(2)
+
+  const findMyWay = FindMyWay({
+    defaultRoute: (req, res) => {
+      t.fail('We should not be here')
+    }
+  })
+
+  findMyWay.on('GET', '/', (req, res) => {
+    t.pass('Yeah!')
+  })
+
+  findMyWay.lookup({
+    method: 'GET',
+    url: '/',
+    headers: { 'accept-version': '2.x' }
+  }, null)
+
+  findMyWay.lookup({
+    method: 'GET',
+    url: '/'
+  }, null)
+})

Vulnerable code: If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route.
  this.versioning = opts.versioning || acceptVersionStrategy
module.exports = {
  storage: SemVerStore,
  deriveVersion: function (req, ctx) {
    return req.headers['accept-version']
Fixed code: If needed you can provide a `version` option, which will allow you to declare multiple versions of the same route. If you never configure a versioned route, the `'Accept-Version'` header will be ignored.

Remember to set a [Vary](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary) header in your responses with the value you are using for deifning the versioning (e.g.: 'Accept-Version'), to prevent cache poisoning attacks. You can also configure this as part your Proxy/CDN.
  this.versioning = opts.versioning || acceptVersionStrategy(false)
  if (version != null && this.versioning.disabled) {
    this.versioning = acceptVersionStrategy(true)
  }
function build (enabled) {
  if (enabled) {
    return {
      storage: SemVerStore,
      deriveVersion: function (req, ctx) {
        return req.headers['accept-version']
      }
    }
  }
  return {
    storage: SemVerStore,
    deriveVersion: function (req, ctx) {},
    disabled: true

module.exports = build

test('Versioning won\'t work if there are no versioned routes', t => {
  t.plan(2)

  const findMyWay = FindMyWay({
    defaultRoute: (req, res) => {
      t.fail('We should not be here')
    }
  })

  findMyWay.on('GET', '/', (req, res) => {
    t.pass('Yeah!')
  })

  findMyWay.lookup({
    method: 'GET',
    url: '/',
    headers: { 'accept-version': '2.x' }
  }, null)

  findMyWay.lookup({
    method: 'GET',
    url: '/'
  }, null)
})