X Tutup
Skip to content

Commit 6e2cf1c

Browse files
committed
Port patch for CVE 2026-29063 onto branch 3.x
1 parent 9e88648 commit 6e2cf1c

File tree

7 files changed

+117
-30
lines changed

7 files changed

+117
-30
lines changed

__tests__/Map.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,9 @@ describe('Map', () => {
353353
expect(is(m1, m2)).toBe(true);
354354
});
355355

356+
it('toJS / toObject are not sensible to prototype pollution', () => {
357+
var m = (Map({ user: 'alice' }) as any).set('__proto__', Map({ admin: true }));
358+
expect(m.toObject().admin).toBeUndefined();
359+
expect(m.toJS().admin).toBeUndefined();
360+
});
356361
});

__tests__/merge.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,41 @@ describe('merge', () => {
149149
expect(m2.getIn(['a', 'b', 0])).toEqual({plain: 'obj'});
150150
})
151151

152+
it('is not sensible to prototype pollution', () => {
153+
var m1 = fromJS({user: 'Alice'});
154+
// Map().set('__proto__', ...) properly creates a __proto__ key in the Map
155+
// (unlike Map({ __proto__: ... }) which triggers JS prototype setter)
156+
var m2 = Map().set('__proto__', Map({ admin: true }));
157+
158+
var r1 = m1.mergeDeep(m2);
159+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
160+
expect(r1.toJS().admin).toBeUndefined();
161+
162+
var r2 = m1.mergeDeepWith((a, b) => b, m2);
163+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
164+
expect(r2.toJS().admin).toBeUndefined();
165+
166+
var r3 = m1.merge(m2);
167+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
168+
expect(r3.toJS().admin).toBeUndefined();
169+
170+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
171+
expect((({}) as any).admin).toBeUndefined();
172+
})
173+
174+
it('is not sensible to prototype pollution via fromJS + JSON.parse', () => {
175+
var userProfile = fromJS({user: 'Alice'});
176+
var requestBody = fromJS(JSON.parse('{"user":"Eve","__proto__":{"admin":true}}'));
177+
178+
var r1 = userProfile.mergeDeep(requestBody);
179+
expect(r1.get('user')).toBe('Eve');
180+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
181+
expect(r1.toJS().admin).toBeUndefined();
182+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
183+
expect(r1.toObject().admin).toBeUndefined();
184+
185+
// @ts-ignore -- testing prototype pollution, ignoring typing errors for tests
186+
expect((({}) as any).admin).toBeUndefined();
187+
})
188+
152189
})

__tests__/updateIn.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,25 @@ describe('updateIn', () => {
278278

279279
})
280280

281+
describe('prototype pollution', () => {
282+
it('setIn on Map with __proto__ key should not pollute toObject result', () => {
283+
var m = Map({profile: Map({bio: 'Hello'})}) as any;
284+
var result = m.setIn(['__proto__', 'admin'], true);
285+
expect(result.toObject().admin).toBeUndefined();
286+
})
287+
288+
it('setIn on Map with nested __proto__ key should not pollute toJS result', () => {
289+
var m = Map({profile: Map({bio: 'Hello'})}) as any;
290+
var result = m.setIn(['profile', '__proto__', 'admin'], true);
291+
expect(result.toJS().profile.admin).toBeUndefined();
292+
})
293+
294+
it('updateIn on Map with __proto__ key should not pollute toObject result', () => {
295+
var m = Map({profile: Map({bio: 'Hello'})}) as any;
296+
// @ts-ignore -- this is testing that we don't allow __proto__ to be used as a key, so we need to bypass the type system.
297+
var result = m.updateIn(['__proto__', 'admin'], () => true);
298+
expect(result.toObject().admin).toBeUndefined();
299+
})
300+
})
301+
281302
})

dist/immutable.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4246,6 +4246,12 @@
42464246
return ctor;
42474247
}
42484248

4249+
function isProtoKey(key) {
4250+
return (
4251+
typeof key === 'string' && (key === '__proto__' || key === 'constructor')
4252+
);
4253+
}
4254+
42494255
Iterable.Iterator = Iterator;
42504256

42514257
mixin(Iterable, {
@@ -4287,7 +4293,13 @@
42874293
toObject: function() {
42884294
assertNotInfinite(this.size);
42894295
var object = {};
4290-
this.__iterate(function(v, k) { object[k] = v; });
4296+
this.__iterate(function(v, k) {
4297+
if (isProtoKey(k)) {
4298+
return;
4299+
}
4300+
4301+
object[k] = v;
4302+
});
42914303
return object;
42924304
},
42934305

0 commit comments

Comments
 (0)
X Tutup