From abbc90df65483add17fd3dce0cf349085c9d1e64 Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Wed, 10 Dec 2025 07:53:08 -0500 Subject: [PATCH] Fix prototype pollution in _.unset and _.omit (#6065) Prevent prototype pollution on baseUnset function by: - Blocking "__proto__" if not an own property - Blocking "constructor.prototype" chains (except when starting at primitive root) - Skipping non-string keys See: https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg --- _baseUnset.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/_baseUnset.js b/_baseUnset.js index 64b7b9cb9..458cc9c47 100644 --- a/_baseUnset.js +++ b/_baseUnset.js @@ -3,6 +3,12 @@ import last from './last.js'; import parent from './_parent.js'; import toKey from './_toKey.js'; +/** Used for built-in method references. */ +var objectProto = Object.prototype; + +/** Used to check objects for own properties. */ +var hasOwnProperty = objectProto.hasOwnProperty; + /** * The base implementation of `_.unset`. * @@ -13,8 +19,47 @@ import toKey from './_toKey.js'; */ 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))]; } export default baseUnset;