Skip to content
This repository was archived by the owner on Apr 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions dottie.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@
// Set nested value
Dottie.set = function(object, path, value, options) {
var pieces = Array.isArray(path) ? path : path.split('.'), current = object, piece, length = pieces.length;
if (pieces[0] === '__proto__') return;

// Guard against prototype pollution at ANY position in the path
// Covers __proto__, constructor, and prototype to prevent all known vectors
var DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
if (pieces.some(function(p) { return DANGEROUS_KEYS.indexOf(p) !== -1; })) return;

if (typeof current !== 'object') {
throw new Error('Parent is not an object.');
}
Expand Down Expand Up @@ -142,7 +146,9 @@
if (key.indexOf(options.delimiter) !== -1) {
pieces = key.split(options.delimiter);

if (pieces[0] === '__proto__') break;
// Guard against prototype pollution at ANY position in the path
var DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
if (pieces.some(function(p) { return DANGEROUS_KEYS.indexOf(p) !== -1; })) break;

piecesLength = pieces.length;
current = transformed;
Expand Down
67 changes: 67 additions & 0 deletions test/set.proto-bypass.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// test/set.proto-bypass.test.js
// Tests for prototype pollution bypass fix (CVE-2023-26132 bypass)

var assert = require('assert');
var dottie = require('../dottie');

describe('dottie.set - prototype pollution bypass prevention', function () {

// === __proto__ at non-first positions ===

it('should block __proto__ at second position', function () {
var obj = {};
dottie.set(obj, 'a.__proto__.polluted', true);

// The property should NOT be reachable via prototype chain
assert.strictEqual(obj.a === undefined || obj.a.polluted === undefined, true,
'__proto__ at position 1 should be blocked');

// Global Object.prototype must remain clean
assert.strictEqual(({}).polluted, undefined,
'Object.prototype must not be polluted');
});

it('should block __proto__ at third position', function () {
var obj = {};
dottie.set(obj, 'a.b.__proto__.polluted', true);
assert.strictEqual(({}).polluted, undefined,
'Object.prototype must not be polluted');
});

it('should still block __proto__ at first position (original CVE-2023-26132 fix)', function () {
var obj = {};
dottie.set(obj, '__proto__.polluted', true);
assert.strictEqual(({}).polluted, undefined,
'Object.prototype must not be polluted');
});

// === constructor and prototype keys ===

it('should block constructor at any position', function () {
var obj = {};
dottie.set(obj, 'a.constructor.prototype.polluted', true);
assert.strictEqual(({}).polluted, undefined,
'constructor-based pollution must be blocked');
});

it('should block prototype at any position', function () {
var obj = {};
dottie.set(obj, 'a.prototype.polluted', true);
assert.strictEqual(({}).polluted, undefined,
'prototype-based pollution must be blocked');
});

// === Legitimate paths should still work ===

it('should allow normal nested paths', function () {
var obj = {};
dottie.set(obj, 'a.b.c', 'hello');
assert.strictEqual(obj.a.b.c, 'hello');
});

it('should allow paths with similar-looking but safe key names', function () {
var obj = {};
dottie.set(obj, 'user.proto.value', 42);
assert.strictEqual(obj.user.proto.value, 42);
});
});
69 changes: 69 additions & 0 deletions test/transform.proto-bypass.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// test/transform.proto-bypass.test.js
// Tests for prototype pollution bypass fix in transform() (CVE-2023-26132 bypass)

var assert = require('assert');
var dottie = require('../dottie');

describe('dottie.transform - prototype pollution bypass prevention', function () {

// === __proto__ at non-first positions ===

it('should block __proto__ at second position in keys', function () {
var flat = { 'user.__proto__.isAdmin': true, 'user.name': 'guest' };
var result = dottie.transform(flat);

// The isAdmin property should NOT be reachable via prototype chain
assert.strictEqual(
result.user === undefined || result.user.isAdmin === undefined, true,
'__proto__ bypass in transform keys should be blocked'
);

// Global Object.prototype must remain clean
assert.strictEqual(({}).isAdmin, undefined,
'Object.prototype must not be polluted');
});

it('should block __proto__ at third position in keys', function () {
var flat = { 'a.b.__proto__.polluted': true };
var result = dottie.transform(flat);
assert.strictEqual(({}).polluted, undefined,
'Object.prototype must not be polluted');
});

it('should still block __proto__ at first position (original fix)', function () {
var flat = { '__proto__.polluted': true };
var result = dottie.transform(flat);
assert.strictEqual(({}).polluted, undefined,
'Object.prototype must not be polluted');
});

// === constructor and prototype keys ===

it('should block constructor-based pollution in transform keys', function () {
var flat = { 'a.constructor.prototype.polluted': true };
var result = dottie.transform(flat);
assert.strictEqual(({}).polluted, undefined,
'constructor-based pollution must be blocked');
});

it('should block prototype key in transform keys', function () {
var flat = { 'a.prototype.polluted': true };
var result = dottie.transform(flat);
assert.strictEqual(({}).polluted, undefined,
'prototype-based pollution must be blocked');
});

// === Legitimate transforms should still work ===

it('should transform normal dotted keys correctly', function () {
var flat = {
'user.name': 'Alice',
'user.email': 'alice@example.com',
'user.settings.theme': 'dark'
};
var result = dottie.transform(flat);
assert.strictEqual(result.user.name, 'Alice');
assert.strictEqual(result.user.email, 'alice@example.com');
assert.strictEqual(result.user.settings.theme, 'dark');
});
});