Prototype pollution in set-in
Description
set-in provides the set value of nested associative structure given array of keys. A prototype pollution vulnerability exists in the the npm package set-in (>=2.0.1, < 2.0.5). Despite a previous fix that attempted to mitigate prototype pollution by checking whether user input contained a forbidden key, it is still possible to pollute Object.prototype via a crafted input using Array.prototype. This has been fixed in version 2.0.5.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
set-innpm | >= 2.0.1, < 2.0.5 | 2.0.5 |
Affected products
1- Range: >= 2.0.1, < 2.0.5
Patches
4b8e1dabfdbd3Merge commit from fork
3 files changed · +18 −4
index.js+3 −3 modified@@ -13,8 +13,6 @@ function setIn (object, path, value) { return recursivelySetIn(object, path, value, 0) } -const POLLUTED_KEYS = ['__proto__', 'constructor', 'prototype'] - function recursivelySetIn (object, path, value, index) { if (index === path.length) { return value @@ -25,7 +23,9 @@ function recursivelySetIn (object, path, value, index) { var key = path[index] // CVE-2020-28273 - assert.ok(!POLLUTED_KEYS.includes(key), `setIn: ${key} is disallowed in path due to possible prototype pollution attack.`) + if(key == "constructor" || key == "prototype" || key == "__proto__"){ + throw `setIn: ${key} is disallowed in path due to possible prototype pollution attack.` + } if (key === '-') { assert.ok(Array.isArray(object), 'setIn: "-" in path must correspond to array.')
package.json+1 −1 modified@@ -32,4 +32,4 @@ "tape": "^4.6.0" }, "dependencies": {} -} +} \ No newline at end of file
test/index.js+14 −0 modified@@ -199,3 +199,17 @@ test('prototype pollution', function (t) { t.end() }) +test('resilience against hijacked Array.prototype.includes', function (t) { + const originalIncludes = Array.prototype.includes; + Array.prototype.includes = () => false; + const obj = {}; + + t.throws(() => { + setIn(obj, ['constructor', 'prototype', 'polluted'], 'yes'); + }, 'should still block prototype access even if Array.includes is hijacked'); + + t.notEqual({}.polluted, 'yes', 'global prototype should not be polluted'); + + Array.prototype.includes = originalIncludes; + t.end(); +}); \ No newline at end of file
34842cc02de3clean up and add tests
2 files changed · +17 −8
index.js+8 −7 modified@@ -13,6 +13,8 @@ function setIn (object, path, value) { return recursivelySetIn(object, path, value, 0) } +const POLLUTED_KEYS = ['__proto__', 'constructor', 'prototype'] + function recursivelySetIn (object, path, value, index) { if (index === path.length) { return value @@ -21,7 +23,9 @@ function recursivelySetIn (object, path, value, index) { object = object || {} var key = path[index] - assert.ok(!POLLUTED_KEYS.includes(key), `recursivelySetIn: ${key} is disallowed in path due to possible prototype pollution attack.`) + + // CVE-2020-28273 + assert.ok(!POLLUTED_KEYS.includes(key), `setIn: ${key} is disallowed in path due to possible prototype pollution attack.`) if (key === '-') { assert.ok(Array.isArray(object), 'setIn: "-" in path must correspond to array.') @@ -30,15 +34,12 @@ function recursivelySetIn (object, path, value, index) { var next = recursivelySetIn(object[key], path, value, ++index) - return set(object, key, next) -} + set(object, key, next) -const POLLUTED_KEYS = ['__proto__', 'constructor', 'prototype'] + return object +} function set (object, key, value) { - // CVE-2020-28273 - assert.ok(!POLLUTED_KEYS.includes(key), `setIn: ${key} is disallowed in path due to possible prototype pollution attack.`) - object[key] = value return object }
test/index.js+9 −1 modified@@ -178,16 +178,24 @@ test('object with custom get function', function (t) { test('prototype pollution', function (t) { t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['__proto__'], { a: 'x' })) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, [['__proto__']], { a: 'x' })) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['__proto__', 'a'], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, [['__proto__'], 'a'], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['a', '__proto__'], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['a', ['__proto__']], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor', 'prototype'], { a: 'x' })) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor', 'prototype', 'a'], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['prototype', 'a'], 'x')) + t.notEqual({}.a, 'x') t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor'], 'x')) - t.end() })
d87c1a09fa2echeck all the keys for prototype pollution (#6)
1 file changed · +1 −0
index.js+1 −0 modified@@ -21,6 +21,7 @@ function recursivelySetIn (object, path, value, index) { object = object || {} var key = path[index] + assert.ok(!POLLUTED_KEYS.includes(key), `recursivelySetIn: ${key} is disallowed in path due to possible prototype pollution attack.`) if (key === '-') { assert.ok(Array.isArray(object), 'setIn: "-" in path must correspond to array.')
6bad255961d3better fix for prototype pollution vulnerability
3 files changed · +3359 −27
index.js+9 −12 modified@@ -5,6 +5,10 @@ module.exports = setIn function setIn (object, path, value) { assert.equal(typeof object, 'object', 'setIn: expected object as first argument.') assert.ok(Array.isArray(path), 'setIn: expected array path as second argument.') + assert.ok( + path.every(p => typeof p === 'number' || typeof p === 'string'), + 'setIn: expected array path (of strings and numbers) as second argument.' + ) return recursivelySetIn(object, path, value, 0) } @@ -16,18 +20,6 @@ function recursivelySetIn (object, path, value, index) { object = object || {} - // https://stackoverflow.com/a/60850027 - assert.ok( - path[index] !== '__proto__', - 'setIn: "__proto__" is disallowed in path due to possible prototype pollution attack.' - ) - if (index < path.length - 1) { - assert.ok( - path[index] !== 'constructor' && path[index + 1] !== 'prototype', - 'setIn: ["constructor", "prototype"] is disallowed in path due to possible prototype pollution attack.' - ) - } - var key = path[index] if (key === '-') { @@ -40,7 +32,12 @@ function recursivelySetIn (object, path, value, index) { return set(object, key, next) } +const POLLUTED_KEYS = ['__proto__', 'constructor', 'prototype'] + function set (object, key, value) { + // CVE-2020-28273 + assert.ok(!POLLUTED_KEYS.includes(key), `setIn: ${key} is disallowed in path due to possible prototype pollution attack.`) + object[key] = value return object }
package-lock.json+3344 −13 modifiedtest/index.js+6 −2 modified@@ -178,12 +178,16 @@ test('object with custom get function', function (t) { test('prototype pollution', function (t) { t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['__proto__'], { a: 'x' })) + t.throws(() => setIn({ 'a': { 'b': 'c' }}, [['__proto__']], { a: 'x' })) t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['__proto__', 'a'], 'x')) + t.throws(() => setIn({ 'a': { 'b': 'c' }}, [['__proto__'], 'a'], 'x')) t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['a', '__proto__'], 'x')) + t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['a', ['__proto__']], 'x')) t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor', 'prototype'], { a: 'x' })) t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor', 'prototype', 'a'], 'x')) - setIn({ 'a': { 'b': 'c' }}, ['prototype', 'a'], 'x') - setIn({ 'a': { 'b': 'c' }}, ['constructor'], 'x') + t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['prototype', 'a'], 'x')) + t.throws(() => setIn({ 'a': { 'b': 'c' }}, ['constructor'], 'x')) + t.end() })
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-2c4m-g7rx-63q7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-26021ghsaADVISORY
- github.com/ahdinosaur/set-in/commit/34842cc02de3fd65d6f8bd0b268347e7b390125bghsaWEB
- github.com/ahdinosaur/set-in/commit/6bad255961d379e4b1f5fbc52ef9dc8420816f24ghsaWEB
- github.com/ahdinosaur/set-in/commit/b8e1dabfdbd35c8d604b6324e01d03f280256c3dghsax_refsource_MISCWEB
- github.com/ahdinosaur/set-in/commit/d87c1a09fa2edb55cd76440a67d83d1cb828df11ghsaWEB
- github.com/ahdinosaur/set-in/pull/6ghsaWEB
- github.com/ahdinosaur/set-in/security/advisories/GHSA-2c4m-g7rx-63q7ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.