Skip to content

Commit ec7ab01

Browse files
authored
util.unsetByPath(): prevent prototype modification (#1406)
1 parent 9a51109 commit ec7ab01

File tree

2 files changed

+34
-24
lines changed

2 files changed

+34
-24
lines changed

src/util/util.mjs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,18 @@ export const getByPath = function(obj, path, delimiter) {
137137
return obj;
138138
};
139139

140+
const isGetSafe = function(obj, key) {
141+
// Prevent prototype pollution
142+
// https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399
143+
if (key === 'constructor' && typeof obj[key] === 'function') {
144+
return false;
145+
}
146+
if (key === '__proto__') {
147+
return false;
148+
}
149+
return true;
150+
};
151+
140152
export const setByPath = function(obj, path, value, delimiter) {
141153

142154
const keys = Array.isArray(path) ? path : path.split(delimiter || '/');
@@ -146,15 +158,8 @@ export const setByPath = function(obj, path, value, delimiter) {
146158

147159
for (; i < last; i++) {
148160
const key = keys[i];
161+
if (!isGetSafe(diver, key)) return obj;
149162
const value = diver[key];
150-
// Prevent prototype pollution
151-
// https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399
152-
if (key === 'constructor' && typeof value === 'function') {
153-
return obj;
154-
}
155-
if (key === '__proto__') {
156-
return obj;
157-
}
158163
// diver creates an empty object if there is no nested object under such a key.
159164
// This means that one can populate an empty nested object with setByPath().
160165
diver = value || (diver[key] = {});
@@ -167,25 +172,21 @@ export const setByPath = function(obj, path, value, delimiter) {
167172

168173
export const unsetByPath = function(obj, path, delimiter) {
169174

170-
delimiter || (delimiter = '/');
171-
172-
var pathArray = Array.isArray(path) ? path.slice() : path.split(delimiter);
173-
174-
var propertyToRemove = pathArray.pop();
175-
if (pathArray.length > 0) {
176-
177-
// unsetting a nested attribute
178-
var parent = getByPath(obj, pathArray, delimiter);
179-
if (parent) {
180-
delete parent[propertyToRemove];
181-
}
182-
183-
} else {
175+
const keys = Array.isArray(path) ? path : path.split(delimiter || '/');
176+
const last = keys.length - 1;
177+
let diver = obj;
178+
let i = 0;
184179

185-
// unsetting a primitive attribute
186-
delete obj[propertyToRemove];
180+
for (; i < last; i++) {
181+
const key = keys[i];
182+
if (!isGetSafe(diver, key)) return obj;
183+
const value = diver[key];
184+
if (!value) return obj;
185+
diver = value;
187186
}
188187

188+
delete diver[keys[last]];
189+
189190
return obj;
190191
};
191192

test/jointjs/core/util.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,15 @@ QUnit.module('util', function(hooks) {
401401
joint.util.unsetByPath(obj, ['c', 'd']);
402402
assert.deepEqual(obj, { a: 1 }, 'Attempt to delete non-existing attribute doesn\'t affect object.');
403403
});
404+
405+
['__proto__/toString', 'constructor/prototype/toString'].forEach(function(path) {
406+
QUnit.test('unsetting "' + path + '" does not modify prototype' , function(assert) {
407+
var obj = {};
408+
assert.equal(typeof obj.toString, 'function');
409+
joint.util.unsetByPath({}, path, '/');
410+
assert.equal(typeof obj.toString, 'function');
411+
});
412+
});
404413
});
405414

406415
QUnit.test('util.normalizeSides()', function(assert) {

0 commit comments

Comments
 (0)