Skip to content

Commit ab6f40d

Browse files
committed
test: encode/decode prototype and chain pollution tests
1 parent a7eddf2 commit ab6f40d

File tree

2 files changed

+332
-0
lines changed

2 files changed

+332
-0
lines changed

src/__tests__/decode-test.js

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,192 @@ describe('decode', () => {
120120
count: 15,
121121
});
122122
});
123+
124+
describe('Prototype Pollution Protection', () => {
125+
beforeEach(() => {
126+
// Clear any pollution before each test
127+
delete Object.prototype.polluted;
128+
delete Object.prototype.malicious;
129+
delete Object.prototype.exploit;
130+
});
131+
132+
afterEach(() => {
133+
// Clean up after tests
134+
delete Object.prototype.polluted;
135+
delete Object.prototype.malicious;
136+
delete Object.prototype.exploit;
137+
});
138+
139+
it('should not pollute Object.prototype when decoding object with __proto__ key', () => {
140+
const testObj = {};
141+
const maliciousInput = {
142+
normalKey: 'value',
143+
__proto__: { polluted: 'yes' },
144+
};
145+
146+
const result = decode(maliciousInput);
147+
148+
// Verify Object.prototype was not polluted
149+
expect(testObj.polluted).toBeUndefined();
150+
expect({}.polluted).toBeUndefined();
151+
expect(Object.prototype.polluted).toBeUndefined();
152+
153+
// Verify result only has own property
154+
expect(Object.prototype.hasOwnProperty.call(result, '__proto__')).toBe(false);
155+
expect(result.normalKey).toBe('value');
156+
});
157+
158+
it('should not pollute Object.prototype when decoding object with constructor key', () => {
159+
const testObj = {};
160+
const maliciousInput = {
161+
normalKey: 'value',
162+
constructor: { polluted: 'yes' },
163+
};
164+
165+
const result = decode(maliciousInput);
166+
167+
// Verify Object.prototype was not polluted
168+
expect(testObj.polluted).toBeUndefined();
169+
expect({}.polluted).toBeUndefined();
170+
expect(Object.prototype.polluted).toBeUndefined();
171+
172+
// Verify result doesn't contain constructor from prototype chain
173+
expect(result.normalKey).toBe('value');
174+
});
175+
176+
it('should not pollute Object.prototype when decoding object with prototype key', () => {
177+
const testObj = {};
178+
const maliciousInput = {
179+
normalKey: 'value',
180+
prototype: { polluted: 'yes' },
181+
};
182+
183+
const result = decode(maliciousInput);
184+
185+
// Verify Object.prototype was not polluted
186+
expect(testObj.polluted).toBeUndefined();
187+
expect({}.polluted).toBeUndefined();
188+
expect(Object.prototype.polluted).toBeUndefined();
189+
190+
// Verify result contains only own properties
191+
expect(result.normalKey).toBe('value');
192+
});
193+
194+
it('should not pollute Object.prototype when decoding nested objects with dangerous keys', () => {
195+
const testObj = {};
196+
const maliciousInput = {
197+
nested: {
198+
__proto__: { polluted: 'nested' },
199+
data: 'value',
200+
},
201+
normal: 'key',
202+
};
203+
204+
const result = decode(maliciousInput);
205+
206+
// Verify Object.prototype was not polluted
207+
expect(testObj.polluted).toBeUndefined();
208+
expect({}.polluted).toBeUndefined();
209+
expect(Object.prototype.polluted).toBeUndefined();
210+
211+
// Verify result structure
212+
expect(result.normal).toBe('key');
213+
expect(result.nested).toBeDefined();
214+
expect(result.nested.data).toBe('value');
215+
expect(Object.prototype.hasOwnProperty.call(result.nested, '__proto__')).toBe(false);
216+
});
217+
218+
it('should not pollute Object.prototype when decoding arrays with objects containing dangerous keys', () => {
219+
const testObj = {};
220+
const maliciousInput = [
221+
{ __proto__: { polluted: 'array1' } },
222+
{ constructor: { malicious: 'array2' } },
223+
{ normalKey: 'value' },
224+
];
225+
226+
const result = decode(maliciousInput);
227+
228+
// Verify Object.prototype was not polluted
229+
expect(testObj.polluted).toBeUndefined();
230+
expect(testObj.malicious).toBeUndefined();
231+
expect({}.polluted).toBeUndefined();
232+
expect({}.malicious).toBeUndefined();
233+
expect(Object.prototype.polluted).toBeUndefined();
234+
expect(Object.prototype.malicious).toBeUndefined();
235+
236+
// Verify result array
237+
expect(Array.isArray(result)).toBe(true);
238+
expect(result.length).toBe(3);
239+
expect(result[2].normalKey).toBe('value');
240+
});
241+
242+
it('should only decode own properties, not inherited ones', () => {
243+
const parent = { inherited: 'parent' };
244+
const child = Object.create(parent);
245+
child.own = 'child';
246+
247+
const result = decode(child);
248+
249+
// Should only include own property
250+
expect(result.own).toBe('child');
251+
expect(result.inherited).toBeUndefined();
252+
});
253+
254+
it('should not decode properties from prototype chain', () => {
255+
Object.prototype.exploit = 'malicious';
256+
const obj = { normalKey: 'value' };
257+
258+
const result = decode(obj);
259+
260+
// Should not include prototype property
261+
expect(result.normalKey).toBe('value');
262+
expect(Object.prototype.hasOwnProperty.call(result, 'exploit')).toBe(false);
263+
264+
delete Object.prototype.exploit;
265+
});
266+
267+
it('should not pollute Object.prototype when decoding Parse type with dangerous className', () => {
268+
const testObj = {};
269+
const maliciousInput = {
270+
__type: 'Pointer',
271+
className: '__proto__',
272+
objectId: 'test123',
273+
};
274+
275+
// This should be handled by ParseObject.fromJSON
276+
decode(maliciousInput);
277+
278+
// Verify Object.prototype was not polluted
279+
expect(testObj.polluted).toBeUndefined();
280+
expect({}.polluted).toBeUndefined();
281+
expect(Object.prototype.polluted).toBeUndefined();
282+
});
283+
284+
it('should not pollute Object.prototype when decoding deeply nested dangerous keys', () => {
285+
const testObj = {};
286+
const maliciousInput = {
287+
level1: {
288+
level2: {
289+
level3: {
290+
__proto__: { polluted: 'deep' },
291+
normalData: 'value',
292+
},
293+
},
294+
},
295+
};
296+
297+
const result = decode(maliciousInput);
298+
299+
// Verify Object.prototype was not polluted
300+
expect(testObj.polluted).toBeUndefined();
301+
expect({}.polluted).toBeUndefined();
302+
expect(Object.prototype.polluted).toBeUndefined();
303+
304+
// Verify result structure is preserved (without dangerous keys)
305+
expect(result.level1.level2.level3.normalData).toBe('value');
306+
expect(Object.prototype.hasOwnProperty.call(result.level1.level2.level3, '__proto__')).toBe(
307+
false
308+
);
309+
});
310+
});
123311
});

src/__tests__/encode-test.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,148 @@ describe('encode', () => {
193193
str: 'abc',
194194
});
195195
});
196+
197+
describe('Prototype Pollution Protection', () => {
198+
beforeEach(() => {
199+
// Clear any pollution before each test
200+
delete Object.prototype.polluted;
201+
delete Object.prototype.malicious;
202+
delete Object.prototype.exploit;
203+
});
204+
205+
afterEach(() => {
206+
// Clean up after tests
207+
delete Object.prototype.polluted;
208+
delete Object.prototype.malicious;
209+
delete Object.prototype.exploit;
210+
});
211+
212+
it('should not pollute Object.prototype when encoding object with __proto__ key', () => {
213+
const testObj = {};
214+
const maliciousInput = {
215+
normalKey: 'value',
216+
__proto__: { polluted: 'yes' },
217+
};
218+
219+
const result = encode(maliciousInput);
220+
221+
// Verify Object.prototype was not polluted
222+
expect(testObj.polluted).toBeUndefined();
223+
expect({}.polluted).toBeUndefined();
224+
expect(Object.prototype.polluted).toBeUndefined();
225+
226+
// Verify result only has own property
227+
expect(Object.prototype.hasOwnProperty.call(result, '__proto__')).toBe(false);
228+
expect(result.normalKey).toBe('value');
229+
});
230+
231+
it('should not pollute Object.prototype when encoding object with constructor key', () => {
232+
const testObj = {};
233+
const maliciousInput = {
234+
normalKey: 'value',
235+
constructor: { polluted: 'yes' },
236+
};
237+
238+
const result = encode(maliciousInput);
239+
240+
// Verify Object.prototype was not polluted
241+
expect(testObj.polluted).toBeUndefined();
242+
expect({}.polluted).toBeUndefined();
243+
expect(Object.prototype.polluted).toBeUndefined();
244+
245+
// Verify result doesn't contain constructor from prototype chain
246+
expect(result.normalKey).toBe('value');
247+
});
248+
249+
it('should not pollute Object.prototype when encoding object with prototype key', () => {
250+
const testObj = {};
251+
const maliciousInput = {
252+
normalKey: 'value',
253+
prototype: { polluted: 'yes' },
254+
};
255+
256+
const result = encode(maliciousInput);
257+
258+
// Verify Object.prototype was not polluted
259+
expect(testObj.polluted).toBeUndefined();
260+
expect({}.polluted).toBeUndefined();
261+
expect(Object.prototype.polluted).toBeUndefined();
262+
263+
// Verify result contains only own properties
264+
expect(result.normalKey).toBe('value');
265+
});
266+
267+
it('should not pollute Object.prototype when encoding nested objects with dangerous keys', () => {
268+
const testObj = {};
269+
const maliciousInput = {
270+
nested: {
271+
__proto__: { polluted: 'nested' },
272+
data: 'value',
273+
},
274+
normal: 'key',
275+
};
276+
277+
const result = encode(maliciousInput);
278+
279+
// Verify Object.prototype was not polluted
280+
expect(testObj.polluted).toBeUndefined();
281+
expect({}.polluted).toBeUndefined();
282+
expect(Object.prototype.polluted).toBeUndefined();
283+
284+
// Verify result structure
285+
expect(result.normal).toBe('key');
286+
expect(result.nested).toBeDefined();
287+
expect(result.nested.data).toBe('value');
288+
expect(Object.prototype.hasOwnProperty.call(result.nested, '__proto__')).toBe(false);
289+
});
290+
291+
it('should not pollute Object.prototype when encoding arrays with objects containing dangerous keys', () => {
292+
const testObj = {};
293+
const maliciousInput = [
294+
{ __proto__: { polluted: 'array1' } },
295+
{ constructor: { malicious: 'array2' } },
296+
{ normalKey: 'value' },
297+
];
298+
299+
const result = encode(maliciousInput);
300+
301+
// Verify Object.prototype was not polluted
302+
expect(testObj.polluted).toBeUndefined();
303+
expect(testObj.malicious).toBeUndefined();
304+
expect({}.polluted).toBeUndefined();
305+
expect({}.malicious).toBeUndefined();
306+
expect(Object.prototype.polluted).toBeUndefined();
307+
expect(Object.prototype.malicious).toBeUndefined();
308+
309+
// Verify result array
310+
expect(Array.isArray(result)).toBe(true);
311+
expect(result.length).toBe(3);
312+
expect(result[2].normalKey).toBe('value');
313+
});
314+
315+
it('should only encode own properties, not inherited ones', () => {
316+
const parent = { inherited: 'parent' };
317+
const child = Object.create(parent);
318+
child.own = 'child';
319+
320+
const result = encode(child);
321+
322+
// Should only include own property
323+
expect(result.own).toBe('child');
324+
expect(result.inherited).toBeUndefined();
325+
});
326+
327+
it('should not encode properties from prototype chain', () => {
328+
Object.prototype.exploit = 'malicious';
329+
const obj = { normalKey: 'value' };
330+
331+
const result = encode(obj);
332+
333+
// Should not include prototype property
334+
expect(result.normalKey).toBe('value');
335+
expect(Object.prototype.hasOwnProperty.call(result, 'exploit')).toBe(false);
336+
337+
delete Object.prototype.exploit;
338+
});
339+
});
196340
});

0 commit comments

Comments
 (0)