From 78e8dcf8754ed9d1219628540d69d09bdba5a9be Mon Sep 17 00:00:00 2001 From: TomTensor Date: Mon, 4 May 2026 20:31:02 +0100 Subject: [PATCH] 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 Co-authored-by: Jay --- lib/helpers/formDataToJSON.js | 2 +- tests/unit/helpers/formDataToJSON.test.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/helpers/formDataToJSON.js b/lib/helpers/formDataToJSON.js index bc36d65b..6c6f7049 100644 --- a/lib/helpers/formDataToJSON.js +++ b/lib/helpers/formDataToJSON.js @@ -68,7 +68,7 @@ function formDataToJSON(formData) { return !isNumericKey; } - if (!target[name] || !utils.isObject(target[name])) { + if (!utils.hasOwnProp(target, name) || !utils.isObject(target[name])) { target[name] = []; } diff --git a/tests/unit/helpers/formDataToJSON.test.js b/tests/unit/helpers/formDataToJSON.test.js index 68ab0ab3..c32a50ac 100644 --- a/tests/unit/helpers/formDataToJSON.test.js +++ b/tests/unit/helpers/formDataToJSON.test.js @@ -95,4 +95,25 @@ describe('formDataToJSON', () => { 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; + } + }); });