Skip to content

fix[cloneDeep, clone, assign, omit, pick]: preserve own __proto__ property without setting prototype - #2128

Open
TalhaHunter101 wants to merge 2 commits into
toss:mainfrom
TalhaHunter101:fix/compat-own-proto-property
Open

TalhaHunter101 wants to merge 2 commits into
toss:mainfrom
TalhaHunter101:fix/compat-own-proto-property

Conversation

@TalhaHunter101

Copy link
Copy Markdown

Summary

Fixes #2117.

In JavaScript, setting target[key] = value when key === '__proto__' invokes Object.prototype.__proto__'s setter rather than defining an own property on the target object. This causes two issues:

  1. '__proto__' is dropped from the target object's own keys (Object.hasOwn(result, '__proto__') becomes false).
  2. The target object's prototype is mutated to the given value (Object.getPrototypeOf(result) is set to the value, causing prototype pollution).

Lodash preserves an own __proto__ property as an own data property without setting or mutating the object's prototype. This PR aligns es-toolkit and es-toolkit/compat with Lodash by using Object.defineProperty whenever assigning '__proto__' keys (matching the existing approach in src/compat/util/toPlainObject.ts).

Lodash difference

const obj = JSON.parse('{"__proto__": {"polluted": true}, "a": 1}');

// Lodash:
const lodashResult = _.cloneDeep(obj);
Object.hasOwn(lodashResult, '__proto__'); // true
Object.getPrototypeOf(lodashResult) === Object.prototype; // true
lodashResult.polluted; // undefined

// es-toolkit (before this fix):
const toolkitResult = cloneDeep(obj);
Object.hasOwn(toolkitResult, '__proto__'); // false
Object.getPrototypeOf(toolkitResult); // { polluted: true }
toolkitResult.polluted; // true (prototype polluted!)

The same behavior difference occurred across clone, cloneDeep, cloneDeepWith, assign, assignIn, assignWith, assignInWith, assignValue, omit, omitBy, pick, pickBy, and mapValues.

Contributing policy checks

  1. Compiles against @types/lodash in strict mode:
import { cloneDeep, clone, assign, omit, pick } from 'lodash';

const parsed: Record<string, unknown> = JSON.parse('{"__proto__": {"polluted": true}}');
cloneDeep(parsed);
clone(parsed);
assign({}, parsed);
omit(parsed, []);
pick(parsed, ['__proto__']);

All of these calls typecheck without error under TypeScript strict mode.

  1. Real code permalinks:

Changes

  • Updated assignValue (src/compat/_internal/assignValue.ts) to use Object.defineProperty when setting '__proto__'.
  • Updated clone and cloneDeepWith in src/object/ and src/compat/object/clone.ts to define '__proto__' using Object.defineProperty.
  • Updated assignImpl, assignInImpl, assignWithImpl, and assignInWithImpl in src/compat/object/ to safely define '__proto__'.
  • Updated omit, omitBy, pick, pickBy, and mapValues in both core and compat to preserve own '__proto__' properties.
  • Added regression tests covering own '__proto__' preservation and prototype non-pollution across all 16 corresponding .spec.ts test files.

…perty without setting prototype

In JavaScript, setting `target[key] = value` when `key === '__proto__'` invokes `Object.prototype.__proto__`'s setter rather than defining an own property on the target object. This causes the target object's prototype to be modified, while dropping '__proto__' from the target's own keys.

Use Object.defineProperty when assigning '__proto__' keys in object copy and assignment utilities (assign, assignIn, assignWith, assignInWith, assignValue, clone, cloneDeep, omit, omitBy, pick, pickBy, mapValues) so that own '__proto__' properties are preserved as own properties without polluting or mutating the object's prototype.
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
es-toolkit Error Error Oct 1, 2026 10:20pm UTC

Request Review

This branch had an error being deployed

1 failed deployment
Preview — 64c52334 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

compat cloneDeep, clone, omit, pick and assign turn an own __proto__ key into the result's prototype

1 participant