CVE-2020-7772: Insufficient Information (NVD-CWE-noinfo)
value: This affects the package doc-path before 2.1.2.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2020-11-15T15:15Z
Repository: https://github.com/mrodrig/doc-path
Language: JSON
Files changed: .travis.yml, package-lock.json, package.json, src/path.js, test/tests.js
Commit date: 2020-10-05 22:42:44 -0400
Commit message: Fix issue presented in #14

Diff: diff --git a/.travis.yml b/.travis.yml
index 454ae68..6002d24 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,14 +3,11 @@ env:
     - CC_TEST_REPORTER_ID=bf120a504e138055e0d71e23e1acd2d75d5dfe9fbcdaf0268ee9f4e3a23e5b2f
 language: node_js
 node_js:
   - "14"
   - "12"
-  - "11"
   - "10"
-  - "9"
-  - "8"
 sudo: false
 before_script:
   - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
   - chmod +x ./cc-test-reporter
   - ./cc-test-reporter before-build
diff --git a/package-lock.json b/package-lock.json
index 006f28a..057251c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,8 +1,8 @@
 {
     "name": "doc-path",
-    "version": "2.1.1",
+    "version": "2.1.2",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {
         "@babel/code-frame": {
             "version": "7.0.0",
diff --git a/package.json b/package.json
index cbd5d56..eedafab 100755
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
 {
     "author": "mrodrig",
     "name": "doc-path",
     "description": "A document path library for Node",
-    "version": "2.1.1",
+    "version": "2.1.2",
     "repository": {
         "type": "git",
         "url": "http://github.com/mrodrig/doc-path.git"
     },
     "main": "./src/path.js",
@@ -35,9 +35,9 @@
         "mocha": "7.1.2",
         "nyc": "14.1.1",
         "should": "13.2.3"
     },
     "engines": {
-        "node": ">=6.0"
+        "node": ">=10"
     },
     "license": "MIT"
 }
diff --git a/src/path.js b/src/path.js
index 2646466..e0a0176 100644
--- a/src/path.js
+++ b/src/path.js
@@ -28,33 +28,44 @@ function evaluatePath(document, keyPath) {
     // Otherwise, we can just return value directly
     return document[keyPath];
 }
 
 function setPath(document, keyPath, value) {
+    if (!document) {
+        throw new Error('No document was provided.');
+    } else if (!keyPath) {
+        throw new Error('No keyPath was provided.');
+    }
+
+    // If this is clearly a prototype pollution attempt, then refuse to modify the path
+    if (keyPath.startsWith('__proto__') || keyPath.startsWith('constructor')) {
+        return document;
+    }
+
+    return _setPath(document, keyPath, value);
+}
+
+function _setPath(document, keyPath, value) {
     if (!document) {
         throw new Error('No document was provided.');
     }
 
     let {indexOfDot, currentKey, remainingKeyPath} = computeStateInformation(keyPath);
 
-    // if (currentKey === '__proto__' || currentKey === 'prototype' && Object.prototype.hasOwnProperty.call(document, currentKey)) {
-    if (currentKey === '__proto__') {
-        // Refuse to modify anything on __proto__, return the document
-        return document;
-    } else if (indexOfDot >= 0) {
+    if (indexOfDot >= 0) {
         // If there is a '.' in the keyPath, recur on the subdoc and ...
         if (!document[currentKey] && Array.isArray(document)) {
             // If this is an array and there are multiple levels of keys to iterate over, recur.
-            return document.forEach((doc) => setPath(doc, keyPath, value));
+            return document.forEach((doc) => _setPath(doc, keyPath, value));
         } else if (!document[currentKey]) {
             // If the currentKey doesn't exist yet, populate it
             document[currentKey] = {};
         }
-        setPath(document[currentKey], remainingKeyPath, value);
+        _setPath(document[currentKey], remainingKeyPath, value);
     } else if (Array.isArray(document)) {
         // If this "document" is actually an array, then we can loop over each of the values and set the path
-        return document.forEach((doc) => setPath(doc, remainingKeyPath, value));
+        return document.forEach((doc) => _setPath(doc, remainingKeyPath, value));
     } else {
         // Otherwise, we can set the path directly
         document[keyPath] = value;
     }
 
diff --git a/test/tests.js b/test/tests.js
index ea3455c..6567360 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -216,13 +216,22 @@ describe('doc-path Module', function() {
             done();
         });
 
         it('should protect against prototype pollution via __proto__', (done) => {
             doc = {};
-            path.setPath(doc, '__proto__.polluted', 'yes');
+            path.setPath(doc, '__proto__.polluted', 'prototype-polluted');
             assert.equal(doc.__proto__.polluted, undefined);
             assert.equal(doc.polluted, undefined);
             assert.equal({}.polluted, undefined);
             done();
         });
+
+        it('should protect against prototype pollution via constructor', (done) => {
+            doc = {};
+            path.setPath(doc, 'constructor', 'prototype-polluted');
+            assert.equal(doc.constructor, Object);
+            path.setPath(doc, 'constructor.prototype.test', 'prototype-polluted');
+            assert.equal(doc.test, undefined);
+            done();
+        });
     });
 });

Vulnerable code:   - "11"
  - "9"
  - "8"
    "version": "2.1.1",
    "version": "2.1.1",
        "node": ">=6.0"
    // if (currentKey === '__proto__' || currentKey === 'prototype' && Object.prototype.hasOwnProperty.call(document, currentKey)) {
    if (currentKey === '__proto__') {
        // Refuse to modify anything on __proto__, return the document
        return document;
    } else if (indexOfDot >= 0) {
            return document.forEach((doc) => setPath(doc, keyPath, value));
        setPath(document[currentKey], remainingKeyPath, value);
        return document.forEach((doc) => setPath(doc, remainingKeyPath, value));
            path.setPath(doc, '__proto__.polluted', 'yes');
Fixed code:     "version": "2.1.2",
    "version": "2.1.2",
        "node": ">=10"
    if (!document) {
        throw new Error('No document was provided.');
    } else if (!keyPath) {
        throw new Error('No keyPath was provided.');
    }

    // If this is clearly a prototype pollution attempt, then refuse to modify the path
    if (keyPath.startsWith('__proto__') || keyPath.startsWith('constructor')) {
        return document;
    }

    return _setPath(document, keyPath, value);
}

function _setPath(document, keyPath, value) {
    if (indexOfDot >= 0) {
            return document.forEach((doc) => _setPath(doc, keyPath, value));
        _setPath(document[currentKey], remainingKeyPath, value);
        return document.forEach((doc) => _setPath(doc, remainingKeyPath, value));
            path.setPath(doc, '__proto__.polluted', 'prototype-polluted');

        it('should protect against prototype pollution via constructor', (done) => {
            doc = {};
            path.setPath(doc, 'constructor', 'prototype-polluted');
            assert.equal(doc.constructor, Object);
            path.setPath(doc, 'constructor.prototype.test', 'prototype-polluted');
            assert.equal(doc.test, undefined);
            done();
        });