Prevent prototype pollution on baseUnset function

* test: add tests to prevent security regressions

* sec: prevent prototype pollution on `baseUnset` function

* chore: improve security patch

- Expand both `_.omit` & `_.unset` security tests to loop over `__proto__`, `constructor`, `prototype`
- Only block `__proto__` if not an own property
This commit is contained in:
Ulises Gascón
2025-12-05 19:26:49 +01:00
committed by jdalton
parent 4879a7a7d0
commit edadd45214
4 changed files with 168 additions and 60 deletions

View File

@@ -4370,8 +4370,47 @@
*/
function baseUnset(object, path) {
path = castPath(path, object);
object = parent(object, path);
return object == null || delete object[toKey(last(path))];
// Prevent prototype pollution, see: https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg
var index = -1,
length = path.length;
if (!length) {
return true;
}
var isRootPrimitive = object == null || (typeof object !== 'object' && typeof object !== 'function');
while (++index < length) {
var key = path[index];
// skip non-string keys (e.g., Symbols, numbers)
if (typeof key !== 'string') {
continue;
}
// Always block "__proto__" anywhere in the path if it's not expected
if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) {
return false;
}
// Block "constructor.prototype" chains
if (key === 'constructor' &&
(index + 1) < length &&
typeof path[index + 1] === 'string' &&
path[index + 1] === 'prototype') {
// Allow ONLY when the path starts at a primitive root, e.g., _.unset(0, 'constructor.prototype.a')
if (isRootPrimitive && index === 0) {
continue;
}
return false;
}
}
var obj = parent(object, path);
return obj == null || delete obj[toKey(last(path))];
}
/**