2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00
Files
axios/tests/unit/helpers/formDataToJSON.test.js
T
TomTensor 78e8dcf875 fix(security): defense-in-depth against already-polluted Object.prototype in formDataToJSON (#7413)
* fix(security): harden prototype pollution protection in formDataToJSON

Replace falsy check with hasOwnProp in the intermediate-path branch of
formDataToJSON's buildPath to prevent write-through into inherited objects.

Without this patch, if Object.prototype is already polluted (e.g. via a
third-party library or earlier vulnerability), user-supplied FormData
paths like 'injected.hijack' traverse the inherited object and mutate
Object.prototype in place. With hasOwnProp, the inherited slot is
shadowed by a new own property, keeping writes local to the result.

This is defense-in-depth: the existing __proto__ guard blocks direct
prototype injection, while this change prevents exploitation of an
already-polluted prototype chain.

Closes #7209

* test(security): use defineProperty + toBe in prototype-pollution regression test

---------

Co-authored-by: tommyhgunz14 <tommyhgunz14@users.noreply.github.com>
Co-authored-by: Jay <jasonsaayman@gmail.com>
2026-05-04 21:31:02 +02:00

120 lines
2.9 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import formDataToJSON from '../../../lib/helpers/formDataToJSON.js';
describe('formDataToJSON', () => {
it('should convert a FormData Object to JSON Object', () => {
const formData = new FormData();
formData.append('foo[bar][baz]', '123');
expect(formDataToJSON(formData)).toEqual({
foo: {
bar: {
baz: '123',
},
},
});
});
it('should convert repeatable values as an array', () => {
const formData = new FormData();
formData.append('foo', '1');
formData.append('foo', '2');
expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2'],
});
});
it('should keep repeatable values flat for 3+ entries', () => {
const formData = new FormData();
formData.append('select3', '301');
formData.append('select3', '302');
formData.append('select3', '303');
expect(formDataToJSON(formData)).toEqual({
select3: ['301', '302', '303'],
});
});
it('should keep nested repeatable values flat for 3+ entries', () => {
const formData = new FormData();
formData.append('foo[bar]', '1');
formData.append('foo[bar]', '2');
formData.append('foo[bar]', '3');
expect(formDataToJSON(formData)).toEqual({
foo: {
bar: ['1', '2', '3'],
},
});
});
it('should convert props with empty brackets to arrays', () => {
const formData = new FormData();
formData.append('foo[]', '1');
formData.append('foo[]', '2');
expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2'],
});
});
it('should supported indexed arrays', () => {
const formData = new FormData();
formData.append('foo[0]', '1');
formData.append('foo[1]', '2');
expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2'],
});
});
it('should resist prototype pollution CVE', () => {
const formData = new FormData();
formData.append('foo[0]', '1');
formData.append('foo[1]', '2');
formData.append('__proto__.x', 'hack');
formData.append('constructor.prototype.y', 'value');
expect(formDataToJSON(formData)).toEqual({
foo: ['1', '2'],
constructor: {
prototype: {
y: 'value',
},
},
});
expect({}.x).toEqual(undefined);
expect({}.y).toEqual(undefined);
});
it('should not write through to inherited objects on Object.prototype', () => {
Object.defineProperty(Object.prototype, 'injected', {
value: { hijack: true },
configurable: true,
writable: true,
});
try {
const formData = new FormData();
formData.append('injected.hijack', 'STOLEN');
const result = formDataToJSON(formData);
expect(result.injected).toEqual({ hijack: 'STOLEN' });
expect(Object.prototype.injected.hijack).toBe(true);
} finally {
delete Object.prototype.injected;
}
});
});