CVE-2023-26132: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: Versions of the package dottie before 2.0.4 are vulnerable to Prototype Pollution due to insufficient checks, via the set() function and the current variable in the /dottie.js file.
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 7.5
Published: 2023-06-10T05:15Z
Repository: https://github.com/mickhansen/dottie.js
Language: JavaScript
Files changed: README.md, dottie.js, test/set.test.js, test/transform.test.js
Commit date: 2023-06-08 16:51:18 +0200
Commit message: rudimentary __proto__ guarding

Diff: diff --git a/README.md b/README.md
index 4d3940f..859e680 100644
--- a/README.md
+++ b/README.md
@@ -30,20 +30,23 @@ dottie.get(values, ['some.dot.included', 'key']); // returns 'barfoo'
 ```
 
 *Note: lodash.get() also works fine for this* 
 
 ### Set value
+
 Sets nested value, creates nested structure if needed
 
 ```js
 dottie.set(values, 'some.nested.value', someValue);
 dottie.set(values, ['some.dot.included', 'value'], someValue);
 dottie.set(values, 'some.nested.object', someValue, {
   force: true // force overwrite defined non-object keys into objects if needed
 });
 ```
 
+If you accept arbitrary/user-defined paths to `set` you should call `Object.preventExtensions(values)` first to guard against potential pollution.
+
 ### Transform object
 Transform object from keys with dottie notation to nested objects
 
 ```js
 var values = {
diff --git a/dottie.js b/dottie.js
index e435093..d5aa55b 100644
--- a/dottie.js
+++ b/dottie.js
@@ -70,10 +70,11 @@
   };
 
   // Set nested value
   Dottie.set = function(object, path, value, options) {
     var pieces = Array.isArray(path) ? path : path.split('.'), current = object, piece, length = pieces.length;
+    if (pieces[0] === '__proto__') return;
 
     if (typeof current !== 'object') {
         throw new Error('Parent is not an object.');
     }
 
@@ -138,10 +139,13 @@
     for (i = 0; i < length; i++) {
       key = keys[i];
 
       if (key.indexOf(options.delimiter) !== -1) {
         pieces = key.split(options.delimiter);
+
+        if (pieces[0] === '__proto__') break;
+
         piecesLength = pieces.length;
         current = transformed;
 
         for (var index = 0; index < piecesLength; index++) {
           piece = pieces[index];
diff --git a/test/set.test.js b/test/set.test.js
index b0a39aa..9b611da 100644
--- a/test/set.test.js
+++ b/test/set.test.js
@@ -63,6 +63,14 @@ describe("dottie.set", function () {
     dottie.set(data, 'foo.bar.baz', 'someValue', {
       force: true
     });
     expect(data.foo.bar.baz).to.equal('someValue');
   });
+
+  it('should not attempt to set __proto__', function () {
+    var data = {};
+
+    dottie.set(data, '__proto__.pollution', 'polluted');
+
+    expect(data.__proto__.pollution).to.be.undefined;
+  });
 });
\ No newline at end of file
diff --git a/test/transform.test.js b/test/transform.test.js
index 461b25b..e2fb6dc 100644
--- a/test/transform.test.js
+++ b/test/transform.test.js
@@ -143,6 +143,18 @@ describe("dottie.transform", function () {
 
     expect(transformed.user.email).to.equal('jd@foobar.com');
     expect(transformed.user.location.city).to.equal('Zanzibar City');
     expect(transformed.project.title).to.equal('dottie');
   });
+
+  it("should guard against prototype pollution", function () {
+    var values = {
+      'user.name': 'John Doe',
+      '__proto__.pollution': 'pollution'
+    };
+
+    var transformed = dottie.transform(values);
+    expect(transformed.user).not.to.equal(undefined);
+    expect(transformed.user.name).to.equal('John Doe');
+    expect(transformed.__proto__.pollution).to.be.undefined;
+  });
 });

Fixed code: 
If you accept arbitrary/user-defined paths to `set` you should call `Object.preventExtensions(values)` first to guard against potential pollution.

    if (pieces[0] === '__proto__') return;

        if (pieces[0] === '__proto__') break;


  it('should not attempt to set __proto__', function () {
    var data = {};

    dottie.set(data, '__proto__.pollution', 'polluted');

    expect(data.__proto__.pollution).to.be.undefined;
  });

  it("should guard against prototype pollution", function () {
    var values = {
      'user.name': 'John Doe',
      '__proto__.pollution': 'pollution'
    };

    var transformed = dottie.transform(values);
    expect(transformed.user).not.to.equal(undefined);
    expect(transformed.user.name).to.equal('John Doe');
    expect(transformed.__proto__.pollution).to.be.undefined;
  });