From 516085f3327b583123618b91ed39a72b22e1d0ce Mon Sep 17 00:00:00 2001 From: John-David Dalton Date: Wed, 10 Dec 2025 07:52:48 -0500 Subject: [PATCH] Fix prototype pollution in _.unset and _.omit (#6063) 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 eefc6e37d..05aa28f0c 100644 --- a/_baseUnset.js +++ b/_baseUnset.js @@ -3,6 +3,12 @@ var castPath = require('./_castPath'), parent = require('./_parent'), toKey = require('./_toKey'); +/** 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 @@ var castPath = require('./_castPath'), */ 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))]; } module.exports = baseUnset;