High severityNVD Advisory· Published Sep 17, 2021· Updated Aug 3, 2024
Prototype Pollution in mariocasciaro/object-path
CVE-2021-3805
Description
object-path is vulnerable to Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
object-pathnpm | < 0.11.8 | 0.11.8 |
Affected products
1- Range: unspecified
Patches
14f0903fd7c83Fix prototype pollution vulnerability
4 files changed · +441 −149
index.js+148 −134 modified@@ -1,87 +1,88 @@ -(function (root, factory){ - 'use strict'; +(function (root, factory) { + 'use strict' /*istanbul ignore next:cant test*/ if (typeof module === 'object' && typeof module.exports === 'object') { - module.exports = factory(); + module.exports = factory() } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. - define([], factory); + define([], factory) } else { // Browser globals - root.objectPath = factory(); + root.objectPath = factory() } -})(this, function(){ - 'use strict'; +})(this, function () { + 'use strict' - var toStr = Object.prototype.toString; - function hasOwnProperty(obj, prop) { - if(obj == null) { + var toStr = Object.prototype.toString + + function hasOwnProperty (obj, prop) { + if (obj == null) { return false } //to handle objects with null prototypes (too edge case?) return Object.prototype.hasOwnProperty.call(obj, prop) } - function isEmpty(value){ + function isEmpty (value) { if (!value) { - return true; + return true } if (isArray(value) && value.length === 0) { - return true; + return true } else if (typeof value !== 'string') { - for (var i in value) { - if (hasOwnProperty(value, i)) { - return false; - } + for (var i in value) { + if (hasOwnProperty(value, i)) { + return false } - return true; + } + return true } - return false; + return false } - function toString(type){ - return toStr.call(type); + function toString (type) { + return toStr.call(type) } - function isObject(obj){ - return typeof obj === 'object' && toString(obj) === "[object Object]"; + function isObject (obj) { + return typeof obj === 'object' && toString(obj) === '[object Object]' } - var isArray = Array.isArray || function(obj){ + var isArray = Array.isArray || function (obj) { /*istanbul ignore next:cant test*/ - return toStr.call(obj) === '[object Array]'; + return toStr.call(obj) === '[object Array]' } - function isBoolean(obj){ - return typeof obj === 'boolean' || toString(obj) === '[object Boolean]'; + function isBoolean (obj) { + return typeof obj === 'boolean' || toString(obj) === '[object Boolean]' } - function getKey(key){ - var intKey = parseInt(key); + function getKey (key) { + var intKey = parseInt(key) if (intKey.toString() === key) { - return intKey; + return intKey } - return key; + return key } - function factory(options) { + function factory (options) { options = options || {} - var objectPath = function(obj) { - return Object.keys(objectPath).reduce(function(proxy, prop) { - if(prop === 'create') { - return proxy; + var objectPath = function (obj) { + return Object.keys(objectPath).reduce(function (proxy, prop) { + if (prop === 'create') { + return proxy } /*istanbul ignore else*/ if (typeof objectPath[prop] === 'function') { - proxy[prop] = objectPath[prop].bind(objectPath, obj); + proxy[prop] = objectPath[prop].bind(objectPath, obj) } - return proxy; - }, {}); - }; + return proxy + }, {}) + } var hasShallowProperty if (options.includeInheritedProps) { @@ -94,213 +95,226 @@ } } - function getShallowProperty(obj, prop) { + function getShallowProperty (obj, prop) { if (hasShallowProperty(obj, prop)) { - return obj[prop]; + return obj[prop] + } + } + + var getShallowPropertySafely + if (options.includeInheritedProps) { + getShallowPropertySafely = function (obj, currentPath) { + if (typeof currentPath !== 'string' && typeof currentPath !== 'number') { + currentPath = String(currentPath) + } + var currentValue = getShallowProperty(obj, currentPath) + if (currentPath === '__proto__' || currentPath === 'prototype' || + (currentPath === 'constructor' && typeof currentValue === 'function')) { + throw new Error('For security reasons, object\'s magic properties cannot be set') + } + return currentValue + } + } else { + getShallowPropertySafely = function (obj, currentPath) { + return getShallowProperty(obj, currentPath) } } - function set(obj, path, value, doNotReplace){ + function set (obj, path, value, doNotReplace) { if (typeof path === 'number') { - path = [path]; + path = [path] } if (!path || path.length === 0) { - return obj; + return obj } if (typeof path === 'string') { - return set(obj, path.split('.').map(getKey), value, doNotReplace); - } - var currentPath = path[0]; - if (typeof currentPath !== 'string' && typeof currentPath !== 'number') { - currentPath = String(currentPath) - } - var currentValue = getShallowProperty(obj, currentPath); - if (options.includeInheritedProps && (currentPath === '__proto__' || - (currentPath === 'constructor' && typeof currentValue === 'function'))) { - throw new Error('For security reasons, object\'s magic properties cannot be set') + return set(obj, path.split('.').map(getKey), value, doNotReplace) } + var currentPath = path[0] + var currentValue = getShallowPropertySafely(obj, currentPath) if (path.length === 1) { if (currentValue === void 0 || !doNotReplace) { - obj[currentPath] = value; + obj[currentPath] = value } - return currentValue; + return currentValue } if (currentValue === void 0) { //check if we assume an array - if(typeof path[1] === 'number') { - obj[currentPath] = []; + if (typeof path[1] === 'number') { + obj[currentPath] = [] } else { - obj[currentPath] = {}; + obj[currentPath] = {} } } - return set(obj[currentPath], path.slice(1), value, doNotReplace); + return set(obj[currentPath], path.slice(1), value, doNotReplace) } objectPath.has = function (obj, path) { if (typeof path === 'number') { - path = [path]; + path = [path] } else if (typeof path === 'string') { - path = path.split('.'); + path = path.split('.') } if (!path || path.length === 0) { - return !!obj; + return !!obj } for (var i = 0; i < path.length; i++) { - var j = getKey(path[i]); + var j = getKey(path[i]) - if((typeof j === 'number' && isArray(obj) && j < obj.length) || + if ((typeof j === 'number' && isArray(obj) && j < obj.length) || (options.includeInheritedProps ? (j in Object(obj)) : hasOwnProperty(obj, j))) { - obj = obj[j]; + obj = obj[j] } else { - return false; + return false } } - return true; - }; + return true + } - objectPath.ensureExists = function (obj, path, value){ - return set(obj, path, value, true); - }; + objectPath.ensureExists = function (obj, path, value) { + return set(obj, path, value, true) + } - objectPath.set = function (obj, path, value, doNotReplace){ - return set(obj, path, value, doNotReplace); - }; + objectPath.set = function (obj, path, value, doNotReplace) { + return set(obj, path, value, doNotReplace) + } - objectPath.insert = function (obj, path, value, at){ - var arr = objectPath.get(obj, path); - at = ~~at; + objectPath.insert = function (obj, path, value, at) { + var arr = objectPath.get(obj, path) + at = ~~at if (!isArray(arr)) { - arr = []; - objectPath.set(obj, path, arr); + arr = [] + objectPath.set(obj, path, arr) } - arr.splice(at, 0, value); - }; + arr.splice(at, 0, value) + } - objectPath.empty = function(obj, path) { + objectPath.empty = function (obj, path) { if (isEmpty(path)) { - return void 0; + return void 0 } if (obj == null) { - return void 0; + return void 0 } - var value, i; + var value, i if (!(value = objectPath.get(obj, path))) { - return void 0; + return void 0 } if (typeof value === 'string') { - return objectPath.set(obj, path, ''); + return objectPath.set(obj, path, '') } else if (isBoolean(value)) { - return objectPath.set(obj, path, false); + return objectPath.set(obj, path, false) } else if (typeof value === 'number') { - return objectPath.set(obj, path, 0); + return objectPath.set(obj, path, 0) } else if (isArray(value)) { - value.length = 0; + value.length = 0 } else if (isObject(value)) { for (i in value) { if (hasShallowProperty(value, i)) { - delete value[i]; + delete value[i] } } } else { - return objectPath.set(obj, path, null); + return objectPath.set(obj, path, null) } - }; + } - objectPath.push = function (obj, path /*, values */){ - var arr = objectPath.get(obj, path); + objectPath.push = function (obj, path /*, values */) { + var arr = objectPath.get(obj, path) if (!isArray(arr)) { - arr = []; - objectPath.set(obj, path, arr); + arr = [] + objectPath.set(obj, path, arr) } - arr.push.apply(arr, Array.prototype.slice.call(arguments, 2)); - }; + arr.push.apply(arr, Array.prototype.slice.call(arguments, 2)) + } objectPath.coalesce = function (obj, paths, defaultValue) { - var value; + var value for (var i = 0, len = paths.length; i < len; i++) { if ((value = objectPath.get(obj, paths[i])) !== void 0) { - return value; + return value } } - return defaultValue; - }; + return defaultValue + } - objectPath.get = function (obj, path, defaultValue){ + objectPath.get = function (obj, path, defaultValue) { if (typeof path === 'number') { - path = [path]; + path = [path] } if (!path || path.length === 0) { - return obj; + return obj } if (obj == null) { - return defaultValue; + return defaultValue } if (typeof path === 'string') { - return objectPath.get(obj, path.split('.'), defaultValue); + return objectPath.get(obj, path.split('.'), defaultValue) } - var currentPath = getKey(path[0]); - var nextObj = getShallowProperty(obj, currentPath) + var currentPath = getKey(path[0]) + var nextObj = getShallowPropertySafely(obj, currentPath) if (nextObj === void 0) { - return defaultValue; + return defaultValue } if (path.length === 1) { - return nextObj; + return nextObj } - return objectPath.get(obj[currentPath], path.slice(1), defaultValue); - }; + return objectPath.get(obj[currentPath], path.slice(1), defaultValue) + } - objectPath.del = function del(obj, path) { + objectPath.del = function del (obj, path) { if (typeof path === 'number') { - path = [path]; + path = [path] } if (obj == null) { - return obj; + return obj } if (isEmpty(path)) { - return obj; + return obj } - if(typeof path === 'string') { - return objectPath.del(obj, path.split('.')); + if (typeof path === 'string') { + return objectPath.del(obj, path.split('.')) } - var currentPath = getKey(path[0]); + var currentPath = getKey(path[0]) + getShallowPropertySafely(obj, currentPath) if (!hasShallowProperty(obj, currentPath)) { - return obj; + return obj } - if(path.length === 1) { + if (path.length === 1) { if (isArray(obj)) { - obj.splice(currentPath, 1); + obj.splice(currentPath, 1) } else { - delete obj[currentPath]; + delete obj[currentPath] } } else { - return objectPath.del(obj[currentPath], path.slice(1)); + return objectPath.del(obj[currentPath], path.slice(1)) } - return obj; + return obj } - return objectPath; + return objectPath } - var mod = factory(); - mod.create = factory; + var mod = factory() + mod.create = factory mod.withInheritedProps = factory({includeInheritedProps: true}) - return mod; -}); + return mod +})
README.md+6 −0 modified@@ -13,6 +13,10 @@ Access deep properties using a path ## Changelog +### 0.11.8 + +* **SECURITY FIX**. Fix a prototype pollution vulnerability in the `del()`, `empty()`, `push()`, `insert()` functions when using the "inherited props" mode (e.g. when a new `object-path` instance is created with the `includeInheritedProps` option set to `true` or when using the `withInheritedProps` default instance. To help with preventing this type of vulnerability in the client code, also the `get()` function will now throw an exception if an object's magic properties are accessed. The vulnerability does not exist in the default instance exposed by object path (e.g `objectPath.del()`) if using version >= `0.11.0`. + ### 0.11.6 * **SECURITY FIX**. Fix a circumvention of the security fix released in 0.11.5 when non-string/non-numeric values are used in the path (e.g. `op.withInheritedProps.set({}, [['__proto__'], 'polluted'], true)`) @@ -176,6 +180,8 @@ objectPath.withInheritedProps.get(obj, 'notOwn.prop'); objectPath.set(obj, 'notOwn.prop', 'b'); ``` +**NOTE**: For security reasons `object-path` will throw an exception when trying to access an object's magic properties (e.g. `__proto__`, `constructor`) when in "inherited props" mode. + ### Immutability If you are looking for an *immutable* alternative of this library, you can take a look at: [object-path-immutable](https://github.com/mariocasciaro/object-path-immutable)
SECURITY.md+7 −0 added@@ -0,0 +1,7 @@ + +Reporting a security issue +=========== + +Please report any suspected security vulnerabilities responsibly to protect the users of this package. Try not share them publicly before the issue is confirmed and a fix is produced. + +Send us an email at report @ mario.fyi to privately report any security vulnerability to us.
test.js+280 −15 modified@@ -133,6 +133,43 @@ describe('get', function () { expect(objectPath.get(extended, 'enabled')).to.be.equal(true) expect(objectPath.get(extended, 'one')).to.be.equal(undefined) }) + + it('[security] should not get magic properties in default mode', function () { + expect(objectPath.get({}, '__proto__')).to.be.undefined + expect(objectPath.get({}, [['__proto__']])).to.be.undefined + + function Clazz() {} + Clazz.prototype.test = [] + + expect(objectPath.get(new Clazz(), '__proto__')).to.be.undefined + expect(objectPath.get(new Clazz(), [['__proto__']])).to.be.undefined + expect(objectPath.get(new Clazz(), ['constructor', 'prototype'])).to.be.undefined + }) + + it('[security] should not get magic properties in inheritedProps mode', function () { + expect(function() { + objectPath.withInheritedProps.get({}, '__proto__') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.get({}, [['__proto__']]) + }).to.throw('For security reasons') + + function Clazz() {} + Clazz.prototype.test = 'original' + + expect(function() { + objectPath.withInheritedProps.get(new Clazz(), '__proto__') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.get(new Clazz(), [['__proto__']]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.get(new Clazz(), ['constructor', 'prototype']) + }).to.throw('For security reasons') + }) }) @@ -244,9 +281,15 @@ describe('set', function () { objectPath.set({}, [['__proto__'], 'injected'], 'this is bad') expect(Object.prototype.injected).to.be.undefined + objectPath.set({}, ['__proto__'], {}) + expect(Object.prototype.toString).to.be.a('function') + function Clazz() {} Clazz.prototype.test = 'original' + objectPath.set({}, ['__proto__'], {test: 'this is bad'}) + expect(Clazz.prototype.test).to.be.equal('original') + objectPath.set(new Clazz(), '__proto__.test', 'this is bad') expect(Clazz.prototype.test).to.be.equal('original') @@ -258,9 +301,10 @@ describe('set', function () { }) it('[security] should throw an exception if trying to set magic properties in inheritedProps mode', function () { - expect(function() {objectPath.withInheritedProps.set({}, '__proto__.injected', 'this is bad')}) - .to.throw('For security reasons') - expect(Object.prototype.injected).to.be.undefined + expect(function() { + objectPath.withInheritedProps.set({}, '__proto__.injected', 'this is bad') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') expect(function() { objectPath.withInheritedProps.set({}, [['__proto__'], 'injected'], 'this is bad') @@ -270,21 +314,25 @@ describe('set', function () { function Clazz() {} Clazz.prototype.test = 'original' - expect(function() {objectPath.withInheritedProps.set(new Clazz(), '__proto__.test', 'this is bad')}) - .to.throw('For security reasons') - expect(Clazz.prototype.test).to.be.equal('original') + expect(function() { + objectPath.withInheritedProps.set(new Clazz(), '__proto__.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') - expect(function() {objectPath.withInheritedProps.set(new Clazz(), 'constructor.prototype.test', 'this is bad')}) - .to.throw('For security reasons') - expect(Clazz.prototype.test).to.be.equal('original') + expect(function() { + objectPath.withInheritedProps.set(new Clazz(), 'constructor.prototype.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') - expect(function() {objectPath.withInheritedProps.set({}, 'constructor.prototype.injected', 'this is OK')}) - .to.throw('For security reasons') - expect(Object.prototype.injected).to.be.undefined + expect(function() { + objectPath.withInheritedProps.set({}, 'constructor.prototype.injected', 'this is OK') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') - expect(function() {objectPath.withInheritedProps.set({}, [['constructor'], 'prototype', 'injected'], 'this is bad')}) - .to.throw('For security reasons') - expect(Object.prototype.injected).to.be.undefined + expect(function() { + objectPath.withInheritedProps.set({}, [['constructor'], 'prototype', 'injected'], 'this is bad') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') }) }) @@ -328,6 +376,39 @@ describe('push', function () { expect(obj).to.have.nested.property('b.e.0.0', 'l') }) + it('[security] should not push within prototype properties in default mode', function () { + function Clazz() {} + Clazz.prototype.test = [] + + objectPath.push(new Clazz(), '__proto__.test', 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + + objectPath.push(new Clazz(), [['__proto__'], 'test'], 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + + objectPath.push(new Clazz(), 'constructor.prototype.test', 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + }) + + it('[security] should not push within prototype properties in inheritedProps mode', function () { + function Clazz() {} + Clazz.prototype.test = [] + + expect(function() { + objectPath.withInheritedProps.push(new Clazz(), '__proto__.test', 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.push(new Clazz(), [['__proto__'], 'test'], 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.push(new Clazz(), 'constructor.prototype.test', 'pushed') + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + }) }) @@ -361,6 +442,67 @@ describe('ensureExists', function () { expect(any[1]).to.be.an('object') expect(any[1][1]).to.be.an('object') }) + + it('[security] should not set magic properties in default mode', function () { + objectPath.ensureExists({}, '__proto__.injected', 'this is bad') + expect(Object.prototype.injected).to.be.undefined + + objectPath.ensureExists({}, [['__proto__'], 'injected'], 'this is bad') + expect(Object.prototype.injected).to.be.undefined + + objectPath.ensureExists({}, ['__proto__'], {}) + expect(Object.prototype.toString).to.be.a('function') + + function Clazz() {} + Clazz.prototype.test = 'original' + + objectPath.ensureExists({}, ['__proto__'], {test: 'this is bad'}) + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.ensureExists(new Clazz(), '__proto__.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.ensureExists(new Clazz(), [['__proto__'], 'test'], 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.ensureExists(new Clazz(), 'constructor.prototype.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }) + + it('[security] should throw an exception if trying to set magic properties in inheritedProps mode', function () { + expect(function() {objectPath.withInheritedProps.ensureExists({}, '__proto__.injected', 'this is bad')}) + .to.throw('For security reasons') + expect(Object.prototype.injected).to.be.undefined + + expect(function() { + objectPath.withInheritedProps.ensureExists({}, [['__proto__'], 'injected'], 'this is bad') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') + + function Clazz() {} + Clazz.prototype.test = 'original' + + expect(function() { + objectPath.withInheritedProps.ensureExists(new Clazz(), '__proto__.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + + expect(function() { + objectPath.withInheritedProps.ensureExists(new Clazz(), 'constructor.prototype.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.ensureExists({}, 'constructor.prototype.injected', 'this is OK') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.ensureExists({}, [['constructor'], 'prototype', 'injected'], 'this is bad') + expect(Object.prototype.injected).to.be.undefined + }).to.throw('For security reasons') + }) }) describe('coalesce', function () { @@ -512,6 +654,40 @@ describe('empty', function () { expect(obj.instance.arr).to.be.an('array') expect(obj['function']).to.equal(null) }) + + it('[security] should not empty prototype properties in default mode', function () { + function Clazz() {} + Clazz.prototype.test = 'original' + + objectPath.empty(new Clazz(), '__proto__') + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.empty(new Clazz(), [['__proto__']]) + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.empty(new Clazz(), 'constructor.prototype') + expect(Clazz.prototype.test).to.be.equal('original') + }) + + it('[security] should throw an exception if trying to delete prototype properties in inheritedProps mode', function () { + function Clazz() {} + Clazz.prototype.test = 'original' + + expect(function() { + objectPath.withInheritedProps.empty(new Clazz(), '__proto__') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.empty(new Clazz(), 'constructor.prototype') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.empty({}, [['constructor'], 'prototype']) + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + }) }) describe('del', function () { @@ -588,6 +764,56 @@ describe('del', function () { expect(obj.b.d).to.have.length(0) expect(obj.b.d).to.be.deep.equal([]) }) + + it('[security] should not delete prototype properties in default mode', function () { + objectPath.del({}, '__proto__.valueOf') + expect(Object.prototype.valueOf).to.be.a('function') + + objectPath.del({}, [['__proto__'], 'valueOf']) + expect(Object.prototype.valueOf).to.be.a('function') + + function Clazz() {} + Clazz.prototype.test = 'original' + + objectPath.del(new Clazz(), '__proto__.test') + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.del(new Clazz(), [['__proto__'], 'test']) + expect(Clazz.prototype.test).to.be.equal('original') + + objectPath.del(new Clazz(), 'constructor.prototype.test') + expect(Clazz.prototype.test).to.be.equal('original') + }) + + it('[security] should throw an exception if trying to delete prototype properties in inheritedProps mode', function () { + expect(function() { + objectPath.withInheritedProps.del({}, '__proto__.valueOf') + expect(Object.prototype.valueOf).to.be.a('function') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.del({}, [['__proto__'], 'valueOf']) + expect(Object.prototype.valueOf).to.be.a('function') + }).to.throw('For security reasons') + + function Clazz() {} + Clazz.prototype.test = 'original' + + expect(function() { + objectPath.withInheritedProps.del(new Clazz(), '__proto__.test') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.del(new Clazz(), 'constructor.prototype.test', 'this is bad') + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.del({}, [['constructor'], 'prototype', 'test']) + expect(Clazz.prototype.test).to.be.equal('original') + }).to.throw('For security reasons') + }) }) describe('insert', function () { @@ -630,6 +856,45 @@ describe('insert', function () { 'asdf' ]) }) + + it('[security] should not insert within prototype properties in default mode', function () { + function Clazz() {} + Clazz.prototype.test = [] + + objectPath.insert(new Clazz(), '__proto__.test', 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + + objectPath.insert(new Clazz(), [['__proto__'], 'test'], 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + + objectPath.insert(new Clazz(), 'constructor.prototype.test', 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + }) + + it('[security] should not insert within prototype properties in inheritedProps mode', function () { + function Clazz() {} + Clazz.prototype.test = [] + + expect(function() { + objectPath.withInheritedProps.insert(new Clazz(), '__proto__.test', 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.insert(new Clazz(), [['__proto__'], 'test'], 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.insert(new Clazz(), 'constructor.prototype.test', 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + + expect(function() { + objectPath.withInheritedProps.insert(new Clazz().constructor, 'prototype.test', 'inserted', 0) + expect(Clazz.prototype.test).to.be.deep.equal([]) + }).to.throw('For security reasons') + }) }) describe('has', function () {
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-8v63-cqqc-6r2cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-3805ghsaADVISORY
- github.com/mariocasciaro/object-path/commit/4f0903fd7c832d12ccbe0d9c3d7e25d985e9e884ghsaWEB
- huntr.dev/bounties/571e3baf-7c46-46e3-9003-ba7e4e623053ghsaWEB
- lists.debian.org/debian-lts-announce/2023/01/msg00031.htmlghsamailing-listWEB
- github.com/mariocasciaro/object-path/commit/e6bb638ffdd431176701b3e9024f80050d0ef0a6mitre
News mentions
0No linked articles in our index yet.