CVE-2021-25912
Description
Prototype pollution in dotty 0.0.1–0.1.0 enables denial of service and potential remote code execution.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Prototype pollution in dotty 0.0.1–0.1.0 enables denial of service and potential remote code execution.
Vulnerability
Overview CVE-2021-25912 describes a prototype pollution vulnerability in the dotty npm library, affecting versions 0.0.1 through 0.1.0. The library allows setting nested properties on objects via dot-separated paths. Due to insufficient validation, an attacker can inject properties into the global Object.prototype, leading to unexpected behavior [1][2][3].
Attack
Vector An attacker can exploit this by providing a crafted path (e.g., __proto__.polluted) to the set or get functions. This pollutes the prototype chain of all objects, potentially causing denial of service by overriding built-in methods or properties. The attack requires no authentication if the vulnerable endpoint accepts user-controlled keys [2][3].
Impact
Successful exploitation can result in denial of service through application crashes or infinite loops. Under certain conditions, prototype pollution may lead to remote code execution if the polluted properties affect subsequent code execution paths (e.g., by modifying template engines or environment variables) [2][3].
Mitigation
The issue has been patched in commit cd997d37917186c131be71501a698803f2b7ebdb by adding proper checks to prevent prototype keys from being set. Users should update to a fixed version or apply the patch manually [1][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 |
|---|---|---|
dottynpm | < 0.1.1 | 0.1.1 |
Affected products
2- dotty/dottydescription
Patches
1cd997d379171Fix prototype pollution (#26)
19 files changed · +670 −525
docs/lib/index.html+1 −1 modified@@ -5,7 +5,7 @@ <title>index.js</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"> - <link rel="stylesheet" media="all" href="..\docco.css" /> + <link rel="stylesheet" media="all" href="../docco.css" /> </head> <body> <div id="container">
docs/public/fonts/aller-bold.eot+0 −0 modifieddocs/public/fonts/aller-bold.ttf+0 −0 modifieddocs/public/fonts/aller-bold.woff+0 −0 modifieddocs/public/fonts/aller-light.eot+0 −0 modifieddocs/public/fonts/aller-light.ttf+0 −0 modifieddocs/public/fonts/aller-light.woff+0 −0 modifieddocs/public/fonts/roboto-black.eot+0 −0 modifieddocs/public/fonts/roboto-black.ttf+0 −0 modifieddocs/public/fonts/roboto-black.woff+0 −0 modifiedlib/index.js+53 −30 modified@@ -13,7 +13,7 @@ // Returns `true` if the path can be completely resolved, `false` otherwise. // -var exists = module.exports.exists = function exists(object, path) { +var exists = (module.exports.exists = function exists(object, path) { if (typeof path === "string") { path = path.split("."); } @@ -35,19 +35,19 @@ var exists = module.exports.exists = function exists(object, path) { } else { return exists(object[key], path); } -}; +}); // // These arguments are the same as those for `exists`. // // The return value, however, is the property you're trying to access, or // `undefined` if it can't be found. This means you won't be able to tell -// the difference between an unresolved path and an undefined property, so you +// the difference between an unresolved path and an undefined property, so you // should not use `get` to check for the existence of a property. Use `exists` // instead. // -var get = module.exports.get = function get(object, path) { +var get = (module.exports.get = function get(object, path) { if (typeof path === "string") { path = path.split("."); } @@ -71,7 +71,7 @@ var get = module.exports.get = function get(object, path) { if (path.length) { return get(object[key], path); } -}; +}); // // Arguments are similar to `exists` and `get`, with the exception that path @@ -85,7 +85,7 @@ var get = module.exports.get = function get(object, path) { // match. Action params are value, parent and key. // -var search = module.exports.search = function search(object, path, action) { +var search = (module.exports.search = function search(object, path, action) { if (typeof path === "string") { path = path.split("."); } @@ -111,29 +111,41 @@ var search = module.exports.search = function search(object, path, action) { } if (path.length === 0) { - return Object.keys(object).filter(key.test.bind(key)).map(function(k) { - var value = object[k]; - if(action){ - action(value, object, k); - } - return value; - }); + return Object.keys(object) + .filter(key.test.bind(key)) + .map(function (k) { + var value = object[k]; + if (action) { + action(value, object, k); + } + return value; + }); } else { - return Array.prototype.concat.apply([], Object.keys(object).filter(key.test.bind(key)).map(function(k) { return search(object[k], path, action); })); + return Array.prototype.concat.apply( + [], + Object.keys(object) + .filter(key.test.bind(key)) + .map(function (k) { + return search(object[k], path, action); + }) + ); } -}; +}); // // Perform a search and remove the matched keys. // The return value is the same object argument with modifications. // -var removeSearch = module.exports.removeSearch = function removeSearch(object, path){ - search(object, path, function(value, object, key){ +var removeSearch = (module.exports.removeSearch = function removeSearch( + object, + path +) { + search(object, path, function (value, object, key) { delete object[key]; }); return object; -}; +}); // // The first two arguments for `put` are the same as `exists` and `get`. @@ -147,23 +159,22 @@ var removeSearch = module.exports.removeSearch = function removeSearch(object, p // successfully, or `false` otherwise. // -var put = module.exports.put = function put(object, path, value) { +var put = (module.exports.put = function put(object, path, value) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return false; } - + path = path.slice(); var key = path.shift(); - if (typeof object !== "object" || object === null) { + if (typeof object !== "object" || object === null || key === "__proto__") { return false; } - if (path.length === 0) { object[key] = value; } else { @@ -177,7 +188,7 @@ var put = module.exports.put = function put(object, path, value) { return put(object[key], path, value); } -}; +}); // // `remove` is like `put` in reverse! @@ -186,15 +197,15 @@ var put = module.exports.put = function put(object, path, value) { // successfully, or `false` otherwise. // -var remove = module.exports.remove = function remove(object, path, value) { +var remove = (module.exports.remove = function remove(object, path, value) { if (typeof path === "string") { path = path.split("."); } if (!(path instanceof Array) || path.length === 0) { return false; } - + path = path.slice(); var key = path.shift(); @@ -214,7 +225,7 @@ var remove = module.exports.remove = function remove(object, path, value) { } else { return remove(object[key], path, value); } -}; +}); // // `deepKeys` creates a list of all possible key paths for a given object. @@ -231,7 +242,11 @@ var remove = module.exports.remove = function remove(object, path, value) { // *Note: this will probably explode on recursive objects. Be careful.* // -var deepKeys = module.exports.deepKeys = function deepKeys(object, options, prefix) { +var deepKeys = (module.exports.deepKeys = function deepKeys( + object, + options, + prefix +) { options = options || {}; if (typeof prefix === "undefined") { @@ -250,13 +265,21 @@ var deepKeys = module.exports.deepKeys = function deepKeys(object, options, pref } if (typeof object[k] === "object" && object[k] !== null) { - keys = keys.concat(deepKeys(object[k], {leavesOnly: options.leavesOnly}, prefix.concat([k]))); + keys = keys.concat( + deepKeys( + object[k], + { leavesOnly: options.leavesOnly }, + prefix.concat([k]) + ) + ); } } if (options.asStrings) { - keys = keys.map(function(e) { return e.join("."); }); + keys = keys.map(function (e) { + return e.join("."); + }); } return keys; -}; +});
package-lock.json+20 −20 modified@@ -22,11 +22,11 @@ "integrity": "sha512-QcWBDnnGaT+rgC0wqynznXv0/4hd6nAFdWNs2fN4FvkH2yAnCYVeRU7GIZXNCeUQ955Lufq+TmZcSXiBa1cGQQ==", "dev": true, "requires": { - "commander": "2.14.1", - "fs-extra": "5.0.0", - "highlight.js": "9.12.0", - "marked": "0.3.16", - "underscore": "1.8.3" + "commander": ">= 0.5.2", + "fs-extra": ">= 0.6.0", + "highlight.js": ">= 8.0.x", + "marked": ">= 0.2.7", + "underscore": ">= 1.0.0" } }, "eyes": { @@ -41,9 +41,9 @@ "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "glob": { @@ -52,10 +52,10 @@ "integrity": "sha1-aVxQvdTi+1xdNwsJHziNNwfikac=", "dev": true, "requires": { - "graceful-fs": "3.0.11", - "inherits": "2.0.3", - "minimatch": "1.0.0", - "once": "1.4.0" + "graceful-fs": "^3.0.2", + "inherits": "2", + "minimatch": "^1.0.0", + "once": "^1.3.0" }, "dependencies": { "graceful-fs": { @@ -64,7 +64,7 @@ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "dev": true, "requires": { - "natives": "1.1.1" + "natives": "^1.1.0" } } } @@ -93,7 +93,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "lru-cache": { @@ -114,8 +114,8 @@ "integrity": "sha1-4N0hILSeG3JM6NcUxSCCKpQ4V20=", "dev": true, "requires": { - "lru-cache": "2.7.3", - "sigmund": "1.0.1" + "lru-cache": "2", + "sigmund": "~1.0.0" } }, "natives": { @@ -130,7 +130,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "sigmund": { @@ -157,9 +157,9 @@ "integrity": "sha1-4J6YjOWUygWgjXKrzKNOiNtVkTE=", "dev": true, "requires": { - "diff": "1.0.8", - "eyes": "0.1.8", - "glob": "4.0.6" + "diff": "~1.0.8", + "eyes": "~0.1.6", + "glob": "~4.0.6" } }, "wrappy": {
test/exists-test.js+51 −48 modified@@ -1,62 +1,65 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("exists").addBatch({ - "A simple path": { - "as a string": { - topic: dotty.exists({"a": "b"}, "a"), - "should return true": function(res) { - assert.isTrue(res); +vows + .describe("exists") + .addBatch({ + "A simple path": { + "as a string": { + topic: dotty.exists({ a: "b" }, "a"), + "should return true": function (res) { + assert.isTrue(res); + }, }, - }, - "as an array": { - topic: dotty.exists({"a": "b"}, ["a"]), - "should return true": function(res) { - assert.isTrue(res); + "as an array": { + topic: dotty.exists({ a: "b" }, ["a"]), + "should return true": function (res) { + assert.isTrue(res); + }, }, }, - }, - "A two-level path": { - "as a string": { - topic: dotty.exists({"a": {"b": "c"}}, "a.b"), - "should return true": function(res) { - assert.isTrue(res); + "A two-level path": { + "as a string": { + topic: dotty.exists({ a: { b: "c" } }, "a.b"), + "should return true": function (res) { + assert.isTrue(res); + }, }, - }, - "as an array": { - topic: dotty.exists({"a": {"b": "c"}}, ["a", "b"]), - "should return true": function(res) { - assert.isTrue(res); + "as an array": { + topic: dotty.exists({ a: { b: "c" } }, ["a", "b"]), + "should return true": function (res) { + assert.isTrue(res); + }, }, }, - }, - "An unresolved path": { - "as a string": { - topic: dotty.exists({"a": {"b": "c"}}, "a.x"), - "should return false": function(res) { - assert.isFalse(res); + "An unresolved path": { + "as a string": { + topic: dotty.exists({ a: { b: "c" } }, "a.x"), + "should return false": function (res) { + assert.isFalse(res); + }, }, - }, - "as an array": { - topic: dotty.exists({"a": {"b": "c"}}, ["a", "x"]), - "should return false": function(res) { - assert.isFalse(res); + "as an array": { + topic: dotty.exists({ a: { b: "c" } }, ["a", "x"]), + "should return false": function (res) { + assert.isFalse(res); + }, }, }, - }, - "A property which is literally undefined, but with a resolved path": { - "as a string": { - topic: dotty.exists({"a": {"b": undefined}}, "a.b"), - "should return true": function(res) { - assert.isTrue(res); + "A property which is literally undefined, but with a resolved path": { + "as a string": { + topic: dotty.exists({ a: { b: undefined } }, "a.b"), + "should return true": function (res) { + assert.isTrue(res); + }, }, - }, - "as an array": { - topic: dotty.exists({"a": {"b": undefined}}, ["a", "b"]), - "should return true": function(res) { - assert.isTrue(res); + "as an array": { + topic: dotty.exists({ a: { b: undefined } }, ["a", "b"]), + "should return true": function (res) { + assert.isTrue(res); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/get-test.js+51 −48 modified@@ -1,62 +1,65 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("get").addBatch({ - "A simple path": { - "as a string": { - topic: dotty.get({"a": "b"}, "a"), - "should return the correct value": function(res) { - assert.equal(res, "b"); +vows + .describe("get") + .addBatch({ + "A simple path": { + "as a string": { + topic: dotty.get({ a: "b" }, "a"), + "should return the correct value": function (res) { + assert.equal(res, "b"); + }, }, - }, - "as an array": { - topic: dotty.get({"a": "b"}, ["a"]), - "should return the correct value": function(res) { - assert.equal(res, "b"); + "as an array": { + topic: dotty.get({ a: "b" }, ["a"]), + "should return the correct value": function (res) { + assert.equal(res, "b"); + }, }, }, - }, - "A two-level path": { - "as a string": { - topic: dotty.get({"a": {"b": "c"}}, "a.b"), - "should return the correct value": function(res) { - assert.equal(res, "c"); + "A two-level path": { + "as a string": { + topic: dotty.get({ a: { b: "c" } }, "a.b"), + "should return the correct value": function (res) { + assert.equal(res, "c"); + }, }, - }, - "as an array": { - topic: dotty.get({"a": {"b": "c"}}, ["a", "b"]), - "should return the correct value": function(res) { - assert.equal(res, "c"); + "as an array": { + topic: dotty.get({ a: { b: "c" } }, ["a", "b"]), + "should return the correct value": function (res) { + assert.equal(res, "c"); + }, }, }, - }, - "An unresolved path": { - "as a string": { - topic: dotty.get({"a": {"b": "c"}}, "a.x"), - "should return undefined": function(res) { - assert.isUndefined(res); + "An unresolved path": { + "as a string": { + topic: dotty.get({ a: { b: "c" } }, "a.x"), + "should return undefined": function (res) { + assert.isUndefined(res); + }, }, - }, - "as an array": { - topic: dotty.get({"a": {"b": "c"}}, ["a", "x"]), - "should return undefined": function(res) { - assert.isUndefined(res); + "as an array": { + topic: dotty.get({ a: { b: "c" } }, ["a", "x"]), + "should return undefined": function (res) { + assert.isUndefined(res); + }, }, }, - }, - "A property which is literally undefined, but with a resolved path": { - "as a string": { - topic: dotty.get({"a": {"b": undefined}}, "a.b"), - "should return undefined": function(res) { - assert.isUndefined(res); + "A property which is literally undefined, but with a resolved path": { + "as a string": { + topic: dotty.get({ a: { b: undefined } }, "a.b"), + "should return undefined": function (res) { + assert.isUndefined(res); + }, }, - }, - "as an array": { - topic: dotty.get({"a": {"b": undefined}}, ["a", "b"]), - "should return undefined": function(res) { - assert.isUndefined(res); + "as an array": { + topic: dotty.get({ a: { b: undefined } }, ["a", "b"]), + "should return undefined": function (res) { + assert.isUndefined(res); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/put-test.js+62 −37 modified@@ -1,48 +1,73 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("put").addBatch({ - "A simple path": { - "as a string": { - topic: (function() { var x = {}; dotty.put(x, "a", "b"); return x; }()), - "should set the correct value": function(res) { - assert.equal(res.a, "b"); +vows + .describe("put") + .addBatch({ + "A simple path": { + "as a string": { + topic: (function () { + var x = {}; + dotty.put(x, "a", "b"); + return x; + })(), + "should set the correct value": function (res) { + assert.equal(res.a, "b"); + }, }, - }, - "as an array": { - topic: (function() { var x = {}; dotty.put(x, ["a"], "b"); return x; }()), - "should set the correct value": function(res) { - assert.equal(res.a, "b"); + "as an array": { + topic: (function () { + var x = {}; + dotty.put(x, ["a"], "b"); + return x; + })(), + "should set the correct value": function (res) { + assert.equal(res.a, "b"); + }, }, }, - }, - "A two-level path": { - "as a string": { - topic: (function() { var x = {}; dotty.put(x, "a.b", "c"); return x; }()), - "should set the correct value": function(res) { - assert.equal(res.a.b, "c"); + "A two-level path": { + "as a string": { + topic: (function () { + var x = {}; + dotty.put(x, "a.b", "c"); + return x; + })(), + "should set the correct value": function (res) { + assert.equal(res.a.b, "c"); + }, }, - }, - "as an array": { - topic: (function() { var x = {}; dotty.put(x, ["a", "b"], "c"); return x; }()), - "should set the correct value": function(res) { - assert.equal(res.a.b, "c"); + "as an array": { + topic: (function () { + var x = {}; + dotty.put(x, ["a", "b"], "c"); + return x; + })(), + "should set the correct value": function (res) { + assert.equal(res.a.b, "c"); + }, }, }, - }, - "An interrupted path": { - "as a string": { - topic: (function() { var x = {a: 1}; return dotty.put(x, "a.b", "c"); }()), - "should return false": function(res) { - assert.isFalse(res); + "An interrupted path": { + "as a string": { + topic: (function () { + var x = { a: 1 }; + return dotty.put(x, "a.b", "c"); + })(), + "should return false": function (res) { + assert.isFalse(res); + }, }, - }, - "as an array": { - topic: (function() { var x = {a: 1}; return dotty.put(x, ["a", "b"], "c"); }()), - "should return false": function(res) { - assert.isFalse(res); + "as an array": { + topic: (function () { + var x = { a: 1 }; + return dotty.put(x, ["a", "b"], "c"); + })(), + "should return false": function (res) { + assert.isFalse(res); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/removesearch-test.js+149 −123 modified@@ -1,145 +1,171 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("search").addBatch({ - "A simple path": { - "as a string": { - topic: dotty.removeSearch({"a": "b"}, "a"), - "should return an object with no keys": function(res) { - assert.isObject(res); - assert.equal(Object.keys(res).length, 0, "Object should have no keys"); +vows + .describe("search") + .addBatch({ + "A simple path": { + "as a string": { + topic: dotty.removeSearch({ a: "b" }, "a"), + "should return an object with no keys": function (res) { + assert.isObject(res); + assert.equal( + Object.keys(res).length, + 0, + "Object should have no keys" + ); + }, }, - }, - "as an array": { - topic: dotty.removeSearch({"a": "b"}, ["a"]), - "should return an object with no keys": function(res) { - assert.isObject(res); - assert.equal(Object.keys(res).length, 0, "Object should have no keys"); + "as an array": { + topic: dotty.removeSearch({ a: "b" }, ["a"]), + "should return an object with no keys": function (res) { + assert.isObject(res); + assert.equal( + Object.keys(res).length, + 0, + "Object should have no keys" + ); + }, }, - }, - "as an array with a regex": { - topic: dotty.removeSearch({"a": "b"}, [/a/]), - "should return an object with no keys": function(res) { - assert.isObject(res); - assert.equal(Object.keys(res).length, 0, "Object should have no keys"); + "as an array with a regex": { + topic: dotty.removeSearch({ a: "b" }, [/a/]), + "should return an object with no keys": function (res) { + assert.isObject(res); + assert.equal( + Object.keys(res).length, + 0, + "Object should have no keys" + ); + }, }, }, - }, - "A two-level path": { - "as a string": { - topic: dotty.removeSearch({"a": {"b": "c"}}, "a.b"), - "should return an object with b removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "A two-level path": { + "as a string": { + topic: dotty.removeSearch({ a: { b: "c" } }, "a.b"), + "should return an object with b removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, - }, - "as an array": { - topic: dotty.removeSearch({"a": {"b": "c"}}, ["a", "b"]), - "should return an object with b removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "as an array": { + topic: dotty.removeSearch({ a: { b: "c" } }, ["a", "b"]), + "should return an object with b removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, - }, - "as an array with regexes": { - topic: dotty.removeSearch({"a": {"b": "c"}}, [/a/, /b/]), - "should return an object with b removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "as an array with regexes": { + topic: dotty.removeSearch({ a: { b: "c" } }, [/a/, /b/]), + "should return an object with b removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, }, - }, - "A two-level path matching two values": { - "as a string": { - topic: dotty.removeSearch({"a": {"b": "c", "d": "e"}}, "a.*"), - "should return an object with b & d removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "A two-level path matching two values": { + "as a string": { + topic: dotty.removeSearch({ a: { b: "c", d: "e" } }, "a.*"), + "should return an object with b & d removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, - }, - "as an array": { - topic: dotty.removeSearch({"a": {"b": "c", "d": "e"}}, ["a", "*"]), - "should return an object with b & d removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "as an array": { + topic: dotty.removeSearch({ a: { b: "c", d: "e" } }, ["a", "*"]), + "should return an object with b & d removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, - }, - "as an array with regexes": { - topic: dotty.removeSearch({"a": {"b": "c", "d": "e"}}, [/a/, /.*/]), - "should return an object with b & d removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.equal(Object.keys(res.a), 0, "a has no keys"); + "as an array with regexes": { + topic: dotty.removeSearch({ a: { b: "c", d: "e" } }, [/a/, /.*/]), + "should return an object with b & d removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.equal(Object.keys(res.a), 0, "a has no keys"); + }, }, }, - }, - "A three-level mixed path matching two values": { - "as a string": { - topic: dotty.removeSearch({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, "a.*.x"), - "should return an object with x removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isObject(res.a.b); - assert.isObject(res.a.c); - assert.equal(Object.keys(res.a.b), 0, "b has no keys"); - assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + "A three-level mixed path matching two values": { + "as a string": { + topic: dotty.removeSearch( + { a: { b: { x: "y" }, c: { x: "z" } } }, + "a.*.x" + ), + "should return an object with x removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isObject(res.a.b); + assert.isObject(res.a.c); + assert.equal(Object.keys(res.a.b), 0, "b has no keys"); + assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + }, }, - }, - "as an array": { - topic: dotty.removeSearch({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, ["a", "*", "x"]), - "should return an object with x removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isObject(res.a.b); - assert.isObject(res.a.c); - assert.equal(Object.keys(res.a.b), 0, "b has no keys"); - assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + "as an array": { + topic: dotty.removeSearch({ a: { b: { x: "y" }, c: { x: "z" } } }, [ + "a", + "*", + "x", + ]), + "should return an object with x removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isObject(res.a.b); + assert.isObject(res.a.c); + assert.equal(Object.keys(res.a.b), 0, "b has no keys"); + assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + }, }, - }, - "as an array with regexes": { - topic: dotty.removeSearch({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, [/a/, /.*/, /x/]), - "should return an object with x removed": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isObject(res.a.b); - assert.isObject(res.a.c); - assert.equal(Object.keys(res.a.b), 0, "b has no keys"); - assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + "as an array with regexes": { + topic: dotty.removeSearch({ a: { b: { x: "y" }, c: { x: "z" } } }, [ + /a/, + /.*/, + /x/, + ]), + "should return an object with x removed": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isObject(res.a.b); + assert.isObject(res.a.c); + assert.equal(Object.keys(res.a.b), 0, "b has no keys"); + assert.equal(Object.keys(res.a.c), 0, "c has no keys"); + }, }, }, - }, - "An unresolved path": { - "as a string": { - topic: dotty.removeSearch({"a": {"b": "c"}}, "a.x"), - "should return unmodified object": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isString(res.a.b); - assert.equal(res.a.b, 'c'); + "An unresolved path": { + "as a string": { + topic: dotty.removeSearch({ a: { b: "c" } }, "a.x"), + "should return unmodified object": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isString(res.a.b); + assert.equal(res.a.b, "c"); + }, }, - }, - "as an array": { - topic: dotty.removeSearch({"a": {"b": "c"}}, ["a", "x"]), - "should return unmodified object": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isString(res.a.b); - assert.equal(res.a.b, 'c'); + "as an array": { + topic: dotty.removeSearch({ a: { b: "c" } }, ["a", "x"]), + "should return unmodified object": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isString(res.a.b); + assert.equal(res.a.b, "c"); + }, }, - }, - "as an array with regexes": { - topic: dotty.removeSearch({"a": {"b": "c"}}, [/a/, /x/]), - "should return unmodified object": function(res) { - assert.isObject(res); - assert.isObject(res.a); - assert.isString(res.a.b); - assert.equal(res.a.b, 'c'); + "as an array with regexes": { + topic: dotty.removeSearch({ a: { b: "c" } }, [/a/, /x/]), + "should return unmodified object": function (res) { + assert.isObject(res); + assert.isObject(res.a); + assert.isString(res.a.b); + assert.equal(res.a.b, "c"); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/remove-test.js+62 −37 modified@@ -1,48 +1,73 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("remove").addBatch({ - "A simple path": { - "as a string": { - topic: (function() { var x = {a: 1}; dotty.remove(x, "a"); return x; }()), - "should remove the property": function(res) { - assert.isUndefined(res.a); +vows + .describe("remove") + .addBatch({ + "A simple path": { + "as a string": { + topic: (function () { + var x = { a: 1 }; + dotty.remove(x, "a"); + return x; + })(), + "should remove the property": function (res) { + assert.isUndefined(res.a); + }, }, - }, - "as an array": { - topic: (function() { var x = {a: 1}; dotty.remove(x, ["a"]); return x; }()), - "should remove the property": function(res) { - assert.isUndefined(res.a); + "as an array": { + topic: (function () { + var x = { a: 1 }; + dotty.remove(x, ["a"]); + return x; + })(), + "should remove the property": function (res) { + assert.isUndefined(res.a); + }, }, }, - }, - "A two-level path": { - "as a string": { - topic: (function() { var x = {a: {b: 1}}; dotty.remove(x, "a.b"); return x; }()), - "should remove the property": function(res) { - assert.isUndefined(res.a.b); + "A two-level path": { + "as a string": { + topic: (function () { + var x = { a: { b: 1 } }; + dotty.remove(x, "a.b"); + return x; + })(), + "should remove the property": function (res) { + assert.isUndefined(res.a.b); + }, }, - }, - "as an array": { - topic: (function() { var x = {a: {b: 1}}; dotty.remove(x, ["a", "b"]); return x; }()), - "should remove the property": function(res) { - assert.isUndefined(res.a.b); + "as an array": { + topic: (function () { + var x = { a: { b: 1 } }; + dotty.remove(x, ["a", "b"]); + return x; + })(), + "should remove the property": function (res) { + assert.isUndefined(res.a.b); + }, }, }, - }, - "An interrupted path": { - "as a string": { - topic: (function() { var x = {a: 1}; return dotty.remove(x, "a.b"); }()), - "should return false": function(res) { - assert.isFalse(res); + "An interrupted path": { + "as a string": { + topic: (function () { + var x = { a: 1 }; + return dotty.remove(x, "a.b"); + })(), + "should return false": function (res) { + assert.isFalse(res); + }, }, - }, - "as an array": { - topic: (function() { var x = {a: 1}; return dotty.remove(x, ["a", "b"]); }()), - "should return false": function(res) { - assert.isFalse(res); + "as an array": { + topic: (function () { + var x = { a: 1 }; + return dotty.remove(x, ["a", "b"]); + })(), + "should return false": function (res) { + assert.isFalse(res); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/search-test.js+192 −181 modified@@ -1,193 +1,204 @@ var dotty = require("../lib/index"), - vows = require("vows"), - assert = require("assert"); + vows = require("vows"), + assert = require("assert"); -vows.describe("search").addBatch({ - "A simple path": { - "as a string": { - topic: dotty.search({"a": "b"}, "a"), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "b"); - }, - }, - "as an array": { - topic: dotty.search({"a": "b"}, ["a"]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "b"); - }, - }, - "as an array with a regex": { - topic: dotty.search({"a": "b"}, [/a/]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "b"); - }, - }, - }, - "A two-level path": { - "as a string": { - topic: dotty.search({"a": {"b": "c"}}, "a.b"), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "c"); - }, - }, - "as an array": { - topic: dotty.search({"a": {"b": "c"}}, ["a", "b"]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "c"); +vows + .describe("search") + .addBatch({ + "A simple path": { + "as a string": { + topic: dotty.search({ a: "b" }, "a"), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "b"); + }, + }, + "as an array": { + topic: dotty.search({ a: "b" }, ["a"]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "b"); + }, + }, + "as an array with a regex": { + topic: dotty.search({ a: "b" }, [/a/]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "b"); + }, }, }, - "as an array with regexes": { - topic: dotty.search({"a": {"b": "c"}}, [/a/, /b/]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return one value": function(res) { - assert.equal(res.length, 1); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "c"); + "A two-level path": { + "as a string": { + topic: dotty.search({ a: { b: "c" } }, "a.b"), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "c"); + }, + }, + "as an array": { + topic: dotty.search({ a: { b: "c" } }, ["a", "b"]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "c"); + }, + }, + "as an array with regexes": { + topic: dotty.search({ a: { b: "c" } }, [/a/, /b/]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return one value": function (res) { + assert.equal(res.length, 1); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "c"); + }, }, }, - }, - "A two-level path matching two values": { - "as a string": { - topic: dotty.search({"a": {"b": "c", "d": "e"}}, "a.*"), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "c"); - assert.equal(res[1], "e"); + "A two-level path matching two values": { + "as a string": { + topic: dotty.search({ a: { b: "c", d: "e" } }, "a.*"), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "c"); + assert.equal(res[1], "e"); + }, + }, + "as an array": { + topic: dotty.search({ a: { b: "c", d: "e" } }, ["a", "*"]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct values": function (res) { + assert.equal(res[0], "c"); + assert.equal(res[1], "e"); + }, + }, + "as an array with regexes": { + topic: dotty.search({ a: { b: "c", d: "e" } }, [/a/, /.*/]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct values": function (res) { + assert.equal(res[0], "c"); + assert.equal(res[1], "e"); + }, }, }, - "as an array": { - topic: dotty.search({"a": {"b": "c", "d": "e"}}, ["a", "*"]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct values": function(res) { - assert.equal(res[0], "c"); - assert.equal(res[1], "e"); + "A three-level mixed path matching two values": { + "as a string": { + topic: dotty.search({ a: { b: { x: "y" }, c: { x: "z" } } }, "a.*.x"), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct value": function (res) { + assert.equal(res[0], "y"); + assert.equal(res[1], "z"); + }, + }, + "as an array": { + topic: dotty.search({ a: { b: { x: "y" }, c: { x: "z" } } }, [ + "a", + "*", + "x", + ]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct values": function (res) { + assert.equal(res[0], "y"); + assert.equal(res[1], "z"); + }, + }, + "as an array with regexes": { + topic: dotty.search({ a: { b: { x: "y" }, c: { x: "z" } } }, [ + /a/, + /.*/, + /x/, + ]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return two values": function (res) { + assert.equal(res.length, 2); + }, + "should return the correct values": function (res) { + assert.equal(res[0], "y"); + assert.equal(res[1], "z"); + }, }, }, - "as an array with regexes": { - topic: dotty.search({"a": {"b": "c", "d": "e"}}, [/a/, /.*/]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct values": function(res) { - assert.equal(res[0], "c"); - assert.equal(res[1], "e"); - }, - }, - }, - "A three-level mixed path matching two values": { - "as a string": { - topic: dotty.search({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, "a.*.x"), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct value": function(res) { - assert.equal(res[0], "y"); - assert.equal(res[1], "z"); - }, - }, - "as an array": { - topic: dotty.search({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, ["a", "*", "x"]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct values": function(res) { - assert.equal(res[0], "y"); - assert.equal(res[1], "z"); - }, - }, - "as an array with regexes": { - topic: dotty.search({"a": {"b": {"x": "y"}, "c": {"x": "z"}}}, [/a/, /.*/, /x/]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return two values": function(res) { - assert.equal(res.length, 2); - }, - "should return the correct values": function(res) { - assert.equal(res[0], "y"); - assert.equal(res[1], "z"); - }, - }, - }, - "An unresolved path": { - "as a string": { - topic: dotty.search({"a": {"b": "c"}}, "a.x"), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return zero values": function(res) { - assert.equal(res.length, 0); - }, - }, - "as an array": { - topic: dotty.search({"a": {"b": "c"}}, ["a", "x"]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return zero values": function(res) { - assert.equal(res.length, 0); - }, - }, - "as an array with regexes": { - topic: dotty.search({"a": {"b": "c"}}, [/a/, /x/]), - "should return an array": function(res) { - assert.isArray(res); - }, - "should return zero values": function(res) { - assert.equal(res.length, 0); + "An unresolved path": { + "as a string": { + topic: dotty.search({ a: { b: "c" } }, "a.x"), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return zero values": function (res) { + assert.equal(res.length, 0); + }, + }, + "as an array": { + topic: dotty.search({ a: { b: "c" } }, ["a", "x"]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return zero values": function (res) { + assert.equal(res.length, 0); + }, + }, + "as an array with regexes": { + topic: dotty.search({ a: { b: "c" } }, [/a/, /x/]), + "should return an array": function (res) { + assert.isArray(res); + }, + "should return zero values": function (res) { + assert.equal(res.length, 0); + }, }, }, - }, -}).export(module); + }) + .export(module);
test/security-test.js+29 −0 added@@ -0,0 +1,29 @@ +var dotty = require("../lib/index"), + vows = require("vows"), + assert = require("assert"); + +vows + .describe("security") + .addBatch({ + "When we attempt to update the prototype": { + topic() { + var obj = {}; + dotty.put(obj, "__proto__.polluted", "Muhahahaha"); + return obj; + }, + "it should not update": function (res) { + assert.equal(res.polluted, undefined); + }, + }, + "When we attempt to update the constructor prototype": { + topic() { + var obj = {}; + dotty.put(obj, "constructor.prototype.polluted", "Muhahahaha"); + return obj; + }, + "it should not update": function (res) { + assert.equal(res.polluted, undefined); + }, + }, + }) + .export(module);
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-f5c9-x9j6-87qpghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-25912ghsaADVISORY
- github.com/deoxxa/dotty/commit/cd997d37917186c131be71501a698803f2b7ebdbghsax_refsource_MISCWEB
- www.npmjs.com/package/dottyghsaWEB
- www.whitesourcesoftware.com/vulnerability-database/CVE-2021-25912ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.