CVE-2019-10793
Description
CVE-2019-10793: Prototype Pollution in dot-object before 2.1.3 allows attackers to pollute Object.prototype via __proto__ payloads.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2019-10793: Prototype Pollution in dot-object before 2.1.3 allows attackers to pollute Object.prototype via __proto__ payloads.
Vulnerability
Overview CVE-2019-10793 is a Prototype Pollution vulnerability in the dot-object npm library, affecting versions before 2.1.3. The set function can be abused to add or modify properties on Object.prototype by crafting a path that includes __proto__. This occurs because the library processes dot-separated path strings and assigns values without sanitizing dangerous keys like __proto__, prototype, or constructor [1][3].
Attack
Vector and Prerequisites The attacker supplies a specially crafted path (e.g., __proto__.polluted) to the dot.object() or dot.set() methods, causing the library to recursively traverse and assign properties. No authentication is required beyond the ability to control path/object inputs. The exploit does not require the attacker to have prior access to the target system's internal objects; rather, any application that uses dot-object to transform untrusted user input into nested objects is vulnerable [2][4].
Impact
Successful exploitation leads to Prototype Pollution, allowing the attacker to inject properties into the global Object.prototype. This can affect all objects in the JavaScript runtime, potentially leading to property injection, bypass of security checks, denial of service, or remote code execution depending on the application's context. The vulnerability does not directly expose data but can be a stepping stone for more severe attacks [3].
Mitigation
The fix was released in version 2.1.3, which adds a blacklist check for __proto__, prototype, and constructor in the parsePath function and throws an error if any of these keys are encountered [1][4]. Users should upgrade to at least version 2.1.3. The vulnerability is also tracked in the Snyk database (SNYK-JS-DOTOBJECT-548905) [3].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
dot-objectnpm | < 2.1.3 | 2.1.3 |
Affected products
2- Snyk/dot-objectv5Range: All versions prior to version 2.1.3
Patches
1f76cff5fe6d0guard for possible prototype polution
5 files changed · +188 −119
index.js+80 −57 modified@@ -45,11 +45,22 @@ function isEmptyObject (val) { return Object.keys(val).length === 0 } +const blacklist = ['__proto__', 'prototype', 'constructor'] + function parsePath (path, sep) { if (path.indexOf('[') >= 0) { path = path.replace(/\[/g, '.').replace(/]/g, '') } - return path.split(sep) + + const parts = path.split(sep) + + const check = parts.filter(part => blacklist.indexOf(part) === -1) + + if (check.length !== parts.length) { + throw Error('Refusing to update blacklisted property ' + path) + } + + return parts } var hasOwnProperty = Object.prototype.hasOwnProperty @@ -83,8 +94,7 @@ DotObject.prototype._fill = function (a, obj, v, mod) { var k = a.shift() if (a.length > 0) { - obj[k] = obj[k] || - (this.useArray && isIndex(a[0]) ? [] : {}) + obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {}) if (!isArrayOrObject(obj[k])) { if (this.override) { @@ -102,8 +112,7 @@ DotObject.prototype._fill = function (a, obj, v, mod) { this._fill(a, obj[k], v, mod) } else { - if (!this.override && - isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { + if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { if (!(isArrayOrObject(v) && isEmptyObject(v))) { throw new Error("Trying to redefine non-empty obj['" + k + "']") } @@ -195,7 +204,7 @@ DotObject.prototype.pick = function (path, obj, remove, reindexArray) { for (i = 0; i < keys.length; i++) { key = parseKey(keys[i], obj) if (obj && typeof obj === 'object' && key in obj) { - if (i === (keys.length - 1)) { + if (i === keys.length - 1) { if (remove) { val = obj[key] if (reindexArray && Array.isArray(obj)) { @@ -221,7 +230,9 @@ DotObject.prototype.pick = function (path, obj, remove, reindexArray) { } } if (remove && Array.isArray(obj)) { - obj = obj.filter(function (n) { return n !== undefined }) + obj = obj.filter(function (n) { + return n !== undefined + }) } return obj } @@ -279,7 +290,9 @@ DotObject.prototype._cleanup = function (obj) { keys = this.cleanup[i].split('.') root = keys.splice(0, -1).join('.') ret = root ? this.pick(root, obj) : obj - ret = ret[keys[0]].filter(function (v) { return v !== undefined }) + ret = ret[keys[0]].filter(function (v) { + return v !== undefined + }) this.set(this.cleanup[i], ret, obj) } this.cleanup = [] @@ -336,13 +349,21 @@ DotObject.prototype.move = function (source, target, obj, mods, merge) { * @param {Function|Array} mods * @param {Boolean} merge */ -DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge) { +DotObject.prototype.transfer = function ( + source, + target, + obj1, + obj2, + mods, + merge +) { if (typeof mods === 'function' || Array.isArray(mods)) { - this.set(target, - _process( - this.pick(source, obj1, true), - mods - ), obj2, merge) + this.set( + target, + _process(this.pick(source, obj1, true), mods), + obj2, + merge + ) } else { merge = mods this.set(target, this.pick(source, obj1, true), obj2, merge) @@ -367,16 +388,16 @@ DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge */ DotObject.prototype.copy = function (source, target, obj1, obj2, mods, merge) { if (typeof mods === 'function' || Array.isArray(mods)) { - this.set(target, + this.set( + target, _process( // clone what is picked - JSON.parse( - JSON.stringify( - this.pick(source, obj1, false) - ) - ), + JSON.parse(JSON.stringify(this.pick(source, obj1, false))), mods - ), obj2, merge) + ), + obj2, + merge + ) } else { merge = mods this.set(target, this.pick(source, obj1, false), obj2, merge) @@ -408,7 +429,7 @@ DotObject.prototype.set = function (path, val, obj, merge) { for (i = 0; i < keys.length; i++) { key = keys[i] - if (i === (keys.length - 1)) { + if (i === keys.length - 1) { if (merge && isObject(val) && isObject(obj[key])) { for (k in val) { if (hasOwnProperty.call(val, k)) { @@ -447,14 +468,14 @@ DotObject.prototype.set = function (path, val, obj, merge) { * * var obj = { * "id": 1, - * "some": { - * "thing": "else" - * } + * "some": { + * "thing": "else" + * } * } * * var transform = { * "id": "nr", - * "some.thing": "name" + * "some.thing": "name" * } * * var tgt = dot.transform(transform, obj) @@ -466,9 +487,11 @@ DotObject.prototype.set = function (path, val, obj, merge) { DotObject.prototype.transform = function (recipe, obj, tgt) { obj = obj || {} tgt = tgt || {} - Object.keys(recipe).forEach(function (key) { - this.set(recipe[key], this.pick(key, obj), tgt) - }.bind(this)) + Object.keys(recipe).forEach( + function (key) { + this.set(recipe[key], this.pick(key, obj), tgt) + }.bind(this) + ) return tgt } @@ -494,31 +517,33 @@ DotObject.prototype.dot = function (obj, tgt, path) { path = path || [] var isArray = Array.isArray(obj) - Object.keys(obj).forEach(function (key) { - var index = isArray && this.useBrackets ? '[' + key + ']' : key - if ( - ( + Object.keys(obj).forEach( + function (key) { + var index = isArray && this.useBrackets ? '[' + key + ']' : key + if ( isArrayOrObject(obj[key]) && - ( - (isObject(obj[key]) && !isEmptyObject(obj[key])) || - (Array.isArray(obj[key]) && (!this.keepArray && (obj[key].length !== 0))) - ) - ) - ) { - if (isArray && this.useBrackets) { - var previousKey = path[path.length - 1] || '' - return this.dot(obj[key], tgt, path.slice(0, -1).concat(previousKey + index)) - } else { - return this.dot(obj[key], tgt, path.concat(index)) - } - } else { - if (isArray && this.useBrackets) { - tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] + ((isObject(obj[key]) && !isEmptyObject(obj[key])) || + (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0)) + ) { + if (isArray && this.useBrackets) { + var previousKey = path[path.length - 1] || '' + return this.dot( + obj[key], + tgt, + path.slice(0, -1).concat(previousKey + index) + ) + } else { + return this.dot(obj[key], tgt, path.concat(index)) + } } else { - tgt[path.concat(index).join(this.separator)] = obj[key] + if (isArray && this.useBrackets) { + tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] + } else { + tgt[path.concat(index).join(this.separator)] = obj[key] + } } - } - }.bind(this)) + }.bind(this) + ) return tgt } @@ -532,9 +557,8 @@ DotObject.str = wrap('str') DotObject.set = wrap('set') DotObject.delete = wrap('delete') DotObject.del = DotObject.remove = wrap('remove') -DotObject.dot = wrap('dot') - -;['override', 'overwrite'].forEach(function (prop) { +DotObject.dot = wrap('dot'); +['override', 'overwrite'].forEach(function (prop) { Object.defineProperty(DotObject, prop, { get: function () { return dotDefault.override @@ -543,9 +567,8 @@ DotObject.dot = wrap('dot') dotDefault.override = !!val } }) -}) - -;['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { +}); +['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { Object.defineProperty(DotObject, prop, { get: function () { return dotDefault[prop]
src/dot-object.js+80 −57 modified@@ -45,11 +45,22 @@ function isEmptyObject (val) { return Object.keys(val).length === 0 } +const blacklist = ['__proto__', 'prototype', 'constructor'] + function parsePath (path, sep) { if (path.indexOf('[') >= 0) { path = path.replace(/\[/g, '.').replace(/]/g, '') } - return path.split(sep) + + const parts = path.split(sep) + + const check = parts.filter(part => blacklist.indexOf(part) === -1) + + if (check.length !== parts.length) { + throw Error('Refusing to update blacklisted property ' + path) + } + + return parts } var hasOwnProperty = Object.prototype.hasOwnProperty @@ -83,8 +94,7 @@ DotObject.prototype._fill = function (a, obj, v, mod) { var k = a.shift() if (a.length > 0) { - obj[k] = obj[k] || - (this.useArray && isIndex(a[0]) ? [] : {}) + obj[k] = obj[k] || (this.useArray && isIndex(a[0]) ? [] : {}) if (!isArrayOrObject(obj[k])) { if (this.override) { @@ -102,8 +112,7 @@ DotObject.prototype._fill = function (a, obj, v, mod) { this._fill(a, obj[k], v, mod) } else { - if (!this.override && - isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { + if (!this.override && isArrayOrObject(obj[k]) && !isEmptyObject(obj[k])) { if (!(isArrayOrObject(v) && isEmptyObject(v))) { throw new Error("Trying to redefine non-empty obj['" + k + "']") } @@ -195,7 +204,7 @@ DotObject.prototype.pick = function (path, obj, remove, reindexArray) { for (i = 0; i < keys.length; i++) { key = parseKey(keys[i], obj) if (obj && typeof obj === 'object' && key in obj) { - if (i === (keys.length - 1)) { + if (i === keys.length - 1) { if (remove) { val = obj[key] if (reindexArray && Array.isArray(obj)) { @@ -221,7 +230,9 @@ DotObject.prototype.pick = function (path, obj, remove, reindexArray) { } } if (remove && Array.isArray(obj)) { - obj = obj.filter(function (n) { return n !== undefined }) + obj = obj.filter(function (n) { + return n !== undefined + }) } return obj } @@ -279,7 +290,9 @@ DotObject.prototype._cleanup = function (obj) { keys = this.cleanup[i].split('.') root = keys.splice(0, -1).join('.') ret = root ? this.pick(root, obj) : obj - ret = ret[keys[0]].filter(function (v) { return v !== undefined }) + ret = ret[keys[0]].filter(function (v) { + return v !== undefined + }) this.set(this.cleanup[i], ret, obj) } this.cleanup = [] @@ -336,13 +349,21 @@ DotObject.prototype.move = function (source, target, obj, mods, merge) { * @param {Function|Array} mods * @param {Boolean} merge */ -DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge) { +DotObject.prototype.transfer = function ( + source, + target, + obj1, + obj2, + mods, + merge +) { if (typeof mods === 'function' || Array.isArray(mods)) { - this.set(target, - _process( - this.pick(source, obj1, true), - mods - ), obj2, merge) + this.set( + target, + _process(this.pick(source, obj1, true), mods), + obj2, + merge + ) } else { merge = mods this.set(target, this.pick(source, obj1, true), obj2, merge) @@ -367,16 +388,16 @@ DotObject.prototype.transfer = function (source, target, obj1, obj2, mods, merge */ DotObject.prototype.copy = function (source, target, obj1, obj2, mods, merge) { if (typeof mods === 'function' || Array.isArray(mods)) { - this.set(target, + this.set( + target, _process( // clone what is picked - JSON.parse( - JSON.stringify( - this.pick(source, obj1, false) - ) - ), + JSON.parse(JSON.stringify(this.pick(source, obj1, false))), mods - ), obj2, merge) + ), + obj2, + merge + ) } else { merge = mods this.set(target, this.pick(source, obj1, false), obj2, merge) @@ -408,7 +429,7 @@ DotObject.prototype.set = function (path, val, obj, merge) { for (i = 0; i < keys.length; i++) { key = keys[i] - if (i === (keys.length - 1)) { + if (i === keys.length - 1) { if (merge && isObject(val) && isObject(obj[key])) { for (k in val) { if (hasOwnProperty.call(val, k)) { @@ -447,14 +468,14 @@ DotObject.prototype.set = function (path, val, obj, merge) { * * var obj = { * "id": 1, - * "some": { - * "thing": "else" - * } + * "some": { + * "thing": "else" + * } * } * * var transform = { * "id": "nr", - * "some.thing": "name" + * "some.thing": "name" * } * * var tgt = dot.transform(transform, obj) @@ -466,9 +487,11 @@ DotObject.prototype.set = function (path, val, obj, merge) { DotObject.prototype.transform = function (recipe, obj, tgt) { obj = obj || {} tgt = tgt || {} - Object.keys(recipe).forEach(function (key) { - this.set(recipe[key], this.pick(key, obj), tgt) - }.bind(this)) + Object.keys(recipe).forEach( + function (key) { + this.set(recipe[key], this.pick(key, obj), tgt) + }.bind(this) + ) return tgt } @@ -494,31 +517,33 @@ DotObject.prototype.dot = function (obj, tgt, path) { path = path || [] var isArray = Array.isArray(obj) - Object.keys(obj).forEach(function (key) { - var index = isArray && this.useBrackets ? '[' + key + ']' : key - if ( - ( + Object.keys(obj).forEach( + function (key) { + var index = isArray && this.useBrackets ? '[' + key + ']' : key + if ( isArrayOrObject(obj[key]) && - ( - (isObject(obj[key]) && !isEmptyObject(obj[key])) || - (Array.isArray(obj[key]) && (!this.keepArray && (obj[key].length !== 0))) - ) - ) - ) { - if (isArray && this.useBrackets) { - var previousKey = path[path.length - 1] || '' - return this.dot(obj[key], tgt, path.slice(0, -1).concat(previousKey + index)) - } else { - return this.dot(obj[key], tgt, path.concat(index)) - } - } else { - if (isArray && this.useBrackets) { - tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] + ((isObject(obj[key]) && !isEmptyObject(obj[key])) || + (Array.isArray(obj[key]) && !this.keepArray && obj[key].length !== 0)) + ) { + if (isArray && this.useBrackets) { + var previousKey = path[path.length - 1] || '' + return this.dot( + obj[key], + tgt, + path.slice(0, -1).concat(previousKey + index) + ) + } else { + return this.dot(obj[key], tgt, path.concat(index)) + } } else { - tgt[path.concat(index).join(this.separator)] = obj[key] + if (isArray && this.useBrackets) { + tgt[path.join(this.separator).concat('[' + key + ']')] = obj[key] + } else { + tgt[path.concat(index).join(this.separator)] = obj[key] + } } - } - }.bind(this)) + }.bind(this) + ) return tgt } @@ -532,9 +557,8 @@ DotObject.str = wrap('str') DotObject.set = wrap('set') DotObject.delete = wrap('delete') DotObject.del = DotObject.remove = wrap('remove') -DotObject.dot = wrap('dot') - -;['override', 'overwrite'].forEach(function (prop) { +DotObject.dot = wrap('dot'); +['override', 'overwrite'].forEach(function (prop) { Object.defineProperty(DotObject, prop, { get: function () { return dotDefault.override @@ -543,9 +567,8 @@ DotObject.dot = wrap('dot') dotDefault.override = !!val } }) -}) - -;['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { +}); +['useArray', 'keepArray', 'useBrackets'].forEach(function (prop) { Object.defineProperty(DotObject, prop, { get: function () { return dotDefault[prop]
test/array_notation.js+6 −0 modified@@ -167,4 +167,10 @@ describe('Dotted Array notation', function () { describe('with bracket notation', function () { runVariant('bracket') }) + + describe('Refuse to update __proto__', function () { + var obj = { path: [] } + + ;(() => Dot.set('path[0].__proto__.toString', 'test', obj)).should.throw(/Refusing to update/) + }) })
test/dot-json.js+7 −0 modified@@ -205,4 +205,11 @@ describe('Object test:', function () { row.should.eql({ page: { name: 'my_page' } }) }) + + it('Dot.object should disallow to set __proto__', function () { + var row = { '__proto__.toString': 'hi' } + + var dot = new Dot() + ;(() => dot.object(row)).should.throw(/Refusing to update/) + }) })
test/str.js+15 −5 modified@@ -34,11 +34,14 @@ describe('str:', function () { obj.should.deepEqual({ a: 1, object: { - fields: [{ - subfield: 'value' - }, { - subfield: 'value1' - }] + fields: [ + { + subfield: 'value' + }, + { + subfield: 'value1' + } + ] } }) }) @@ -50,4 +53,11 @@ describe('str:', function () { a: 'b' }) }) + + it('cannot set __proto__ property', function () { + (() => Dot.str('__proto__.toString', 'hi', {})).should.throw( + /Refusing to update/ + ); + ({}.toString().should.deepEqual('[object Object]')) + }) })
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-j9cf-pr2x-5273ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2019-10793ghsaADVISORY
- github.com/rhalff/dot-object/commit/f76cff5fe6d01d30ce110d8f454db2e5bd28a7deghsax_refsource_MISCWEB
- snyk.io/vuln/SNYK-JS-DOTOBJECT-548905ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.