CVE-2020-7708: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package irrelon-path before 4.7.0; the package @irrelon/path before 4.7.0 are vulnerable to Prototype Pollution via the set, unSet, pushVal and pullVal functions.
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: 9.8
Published: 2020-08-18T15:15Z
Repository: https://github.com/Irrelon/irrelon-path
Language: JavaScript
Files changed: dist/Path.js, package.json, src/Path.js
Commit date: 2020-08-18 15:10:45 +0100
Commit message: Fixed functions with prototype pollution vulnerability

Diff: diff --git a/dist/Path.js b/dist/Path.js
index b7de2a9..a720339 100644
--- a/dist/Path.js
+++ b/dist/Path.js
@@ -449,19 +449,23 @@ var set = function set(obj, path, val) {
     return;
   } // Path has no dot-notation, set key/value
 
 
   if (isNonCompositePath(internalPath)) {
+    // Do not allow prototype pollution
+    if (internalPath === "__proto__") return obj;
     obj = decouple(obj, options);
     obj[options.transformKey(unEscape(internalPath))] = val;
     return obj;
   }
 
   var newObj = decouple(obj, options);
   var pathParts = split(internalPath);
   var pathPart = pathParts.shift();
-  var transformedPathPart = options.transformKey(pathPart);
+  var transformedPathPart = options.transformKey(pathPart); // Do not allow prototype pollution
+
+  if (transformedPathPart === "__proto__") return obj;
   var childPart = newObj[transformedPathPart];
 
   if ((0, _typeof2["default"])(childPart) !== "object") {
     // Create an object or array on the path
     if (String(parseInt(transformedPathPart, 10)) === transformedPathPart) {
@@ -517,22 +521,28 @@ var unSet = function unSet(obj, path) {
   }
 
   var newObj = decouple(obj, options); // Path has no dot-notation, set key/value
 
   if (isNonCompositePath(internalPath)) {
-    if (newObj.hasOwnProperty(unEscape(internalPath))) {
-      delete newObj[options.transformKey(unEscape(internalPath))];
+    var unescapedPath = unEscape(internalPath); // Do not allow prototype pollution
+
+    if (unescapedPath === "__proto__") return obj;
+
+    if (newObj.hasOwnProperty(unescapedPath)) {
+      delete newObj[options.transformKey(unescapedPath)];
       return newObj;
     }
 
     tracking.returnOriginal = true;
     return obj;
   }
 
   var pathParts = split(internalPath);
   var pathPart = pathParts.shift();
-  var transformedPathPart = options.transformKey(unEscape(pathPart));
+  var transformedPathPart = options.transformKey(unEscape(pathPart)); // Do not allow prototype pollution
+
+  if (transformedPathPart === "__proto__") return obj;
   var childPart = newObj[transformedPathPart];
 
   if (!childPart) {
     // No child part available, nothing to unset!
     tracking.returnOriginal = true;
@@ -616,10 +626,11 @@ var pushVal = function pushVal(obj, path, val) {
 
 
   path = clean(path);
   var pathParts = split(path);
   var part = pathParts.shift();
+  if (part === "__proto__") return obj;
 
   if (pathParts.length) {
     // Generate the path part in the object if it does not already exist
     obj[part] = decouple(obj[part], options) || {}; // Recurse
 
@@ -669,10 +680,11 @@ var pullVal = function pullVal(obj, path, val) {
 
 
   path = clean(path);
   var pathParts = split(path);
   var part = pathParts.shift();
+  if (part === "__proto__") return obj;
 
   if (pathParts.length) {
     // Generate the path part in the object if it does not already exist
     obj[part] = decouple(obj[part], options) || {}; // Recurse - we don't need to assign obj[part] the result of this call because
     // we are modifying by reference since we haven't reached the furthest path
diff --git a/package.json b/package.json
index 9c05c5e..8af9017 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
 	"name": "@irrelon/path",
-	"version": "4.6.8",
+	"version": "4.7.0",
 	"description": "A powerful JSON path processor. Allows you to drill into and manipulate JSON objects with a simple dot-delimited path format e.g. \"obj.name\".",
 	"main": "./src/Path.js",
 	"scripts": {
 		"test": "NODE_ENV=test mocha ./tests/**/**.test.js",
 		"testMon": "nodemon --watch src --watch tests --exec \"NODE_ENV=test BABEL_DISABLE_CACHE=1 mocha ./tests/**/**.test.js\"",
diff --git a/src/Path.js b/src/Path.js
index ac1afbe..2597a92 100644
--- a/src/Path.js
+++ b/src/Path.js
@@ -398,19 +398,26 @@ const set = (obj, path, val, options = {}) => {
 		return;
 	}
 	
 	// Path has no dot-notation, set key/value
 	if (isNonCompositePath(internalPath)) {
+		// Do not allow prototype pollution
+		if (internalPath === "__proto__") return obj;
+
 		obj = decouple(obj, options);
 		obj[options.transformKey(unEscape(internalPath))] = val;
 		return obj;
 	}
 	
 	const newObj = decouple(obj, options);
 	const pathParts = split(internalPath);
 	const pathPart = pathParts.shift();
 	const transformedPathPart = options.transformKey(pathPart);
+
+	// Do not allow prototype pollution
+	if (transformedPathPart === "__proto__") return obj;
+
 	let childPart = newObj[transformedPathPart];
 	
 	if (typeof childPart !== "object") {
 		// Create an object or array on the path
 		if (String(parseInt(transformedPathPart, 10)) === transformedPathPart) {
@@ -468,23 +475,31 @@ const unSet = (obj, path, options = {}, tracking = {}) => {
 	
 	const newObj = decouple(obj, options);
 	
 	// Path has no dot-notation, set key/value
 	if (isNonCompositePath(internalPath)) {
-		if (newObj.hasOwnProperty(unEscape(internalPath))) {
-			delete newObj[options.transformKey(unEscape(internalPath))];
+		const unescapedPath = unEscape(internalPath);
+
+		// Do not allow prototype pollution
+		if (unescapedPath === "__proto__") return obj;
+
+		if (newObj.hasOwnProperty(unescapedPath)) {
+			delete newObj[options.transformKey(unescapedPath)];
 			return newObj;
 		}
 		
 		tracking.returnOriginal = true;
 		return obj;
 	}
 	
-	
 	const pathParts = split(internalPath);
 	const pathPart = pathParts.shift();
 	const transformedPathPart = options.transformKey(unEscape(pathPart));
+
+	// Do not allow prototype pollution
+	if (transformedPathPart === "__proto__") return obj;
+
 	let childPart = newObj[transformedPathPart];
 	
 	if (!childPart) {
 		// No child part available, nothing to unset!
 		tracking.returnOriginal = true;
@@ -561,11 +576,13 @@ const pushVal = (obj, path, val, options = {}) => {
 	// Clean the path
 	path = clean(path);
 	
 	const pathParts = split(path);
 	const part = pathParts.shift();
-	
+
+	if (part === "__proto__") return obj;
+
 	if (pathParts.length) {
 		// Generate the path part in the object if it does not already exist
 		obj[part] = decouple(obj[part], options) || {};
 		
 		// Recurse
@@ -611,10 +628,12 @@ const pullVal = (obj, path, val, options = {strict: true}) => {
 	// Clean the path
 	path = clean(path);
 	
 	const pathParts = split(path);
 	const part = pathParts.shift();
+
+	if (part === "__proto__") return obj;
 	
 	if (pathParts.length) {
 		// Generate the path part in the object if it does not already exist
 		obj[part] = decouple(obj[part], options) || {};
 		

Vulnerable code:   var transformedPathPart = options.transformKey(pathPart);
    if (newObj.hasOwnProperty(unEscape(internalPath))) {
      delete newObj[options.transformKey(unEscape(internalPath))];
  var transformedPathPart = options.transformKey(unEscape(pathPart));
	"version": "4.6.8",
		if (newObj.hasOwnProperty(unEscape(internalPath))) {
			delete newObj[options.transformKey(unEscape(internalPath))];
	
	
Fixed code:     // Do not allow prototype pollution
    if (internalPath === "__proto__") return obj;
  var transformedPathPart = options.transformKey(pathPart); // Do not allow prototype pollution

  if (transformedPathPart === "__proto__") return obj;
    var unescapedPath = unEscape(internalPath); // Do not allow prototype pollution

    if (unescapedPath === "__proto__") return obj;

    if (newObj.hasOwnProperty(unescapedPath)) {
      delete newObj[options.transformKey(unescapedPath)];
  var transformedPathPart = options.transformKey(unEscape(pathPart)); // Do not allow prototype pollution

  if (transformedPathPart === "__proto__") return obj;
  if (part === "__proto__") return obj;
  if (part === "__proto__") return obj;
	"version": "4.7.0",
		// Do not allow prototype pollution
		if (internalPath === "__proto__") return obj;


	// Do not allow prototype pollution
	if (transformedPathPart === "__proto__") return obj;

		const unescapedPath = unEscape(internalPath);

		// Do not allow prototype pollution
		if (unescapedPath === "__proto__") return obj;

		if (newObj.hasOwnProperty(unescapedPath)) {
			delete newObj[options.transformKey(unescapedPath)];

	// Do not allow prototype pollution
	if (transformedPathPart === "__proto__") return obj;


	if (part === "__proto__") return obj;


	if (part === "__proto__") return obj;