From 0502a65693bd8f5706d72397dffd3badabb8c94b Mon Sep 17 00:00:00 2001 From: rupayon123 <80724680+rupayon123@users.noreply.github.com> Date: Sat, 13 Jun 2026 18:57:04 -0400 Subject: [PATCH 1/2] fix: preserve proto keys while parsing objects --- src/index.ts | 7 ++++++- tests/examples.test.js | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index ea6977d..2ca5a78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,7 +106,12 @@ const _parseJSON = (jsonString: string, allow: number) => { index++; // skip colon try { const value = parseAny(); - obj[key] = value; + Object.defineProperty(obj, key, { + value, + writable: true, + enumerable: true, + configurable: true, + }); } catch (e) { if (Allow.OBJ & allow) return obj; else throw e; diff --git a/tests/examples.test.js b/tests/examples.test.js index 86c7c02..128f728 100644 --- a/tests/examples.test.js +++ b/tests/examples.test.js @@ -22,6 +22,13 @@ test("obj", () => { expect(parse('{"": "', OBJ)).toEqual({}); expect(parse('{"": "', OBJ | STR)).toEqual({ "": "" }); + const protoValue = { polluted: true }; + const parsed = parse('{"__proto__":{"polluted":true},"safe":1}'); + expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(true); + expect(parsed.__proto__).toEqual(protoValue); + expect(parsed.safe).toBe(1); + expect(Object.prototype.polluted).toBeUndefined(); + expect(() => parse("{", STR)).toThrow(PartialJSON); expect(() => parse('{"', STR)).toThrow(PartialJSON); expect(() => parse('{""', STR)).toThrow(PartialJSON); From c9523fa9323943596e3e3bfc72d2286b2c66a8fd Mon Sep 17 00:00:00 2001 From: rupayon123 <80724680+rupayon123@users.noreply.github.com> Date: Sat, 13 Jun 2026 19:03:44 -0400 Subject: [PATCH 2/2] test: expand proto key coverage --- src/index.ts | 16 ++++++++++------ tests/examples.test.js | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2ca5a78..bd2c7a2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -106,12 +106,16 @@ const _parseJSON = (jsonString: string, allow: number) => { index++; // skip colon try { const value = parseAny(); - Object.defineProperty(obj, key, { - value, - writable: true, - enumerable: true, - configurable: true, - }); + if (key === "__proto__") { + Object.defineProperty(obj, key, { + value, + writable: true, + enumerable: true, + configurable: true, + }); + } else { + obj[key] = value; + } } catch (e) { if (Allow.OBJ & allow) return obj; else throw e; diff --git a/tests/examples.test.js b/tests/examples.test.js index 128f728..b7bec45 100644 --- a/tests/examples.test.js +++ b/tests/examples.test.js @@ -29,6 +29,25 @@ test("obj", () => { expect(parsed.safe).toBe(1); expect(Object.prototype.polluted).toBeUndefined(); + const parsedPrimitive = parse('{"__proto__":1,"safe":1}'); + expect(Object.prototype.hasOwnProperty.call(parsedPrimitive, "__proto__")).toBe(true); + expect(parsedPrimitive.__proto__).toBe(1); + expect(parsedPrimitive.safe).toBe(1); + expect(Object.prototype.polluted).toBeUndefined(); + + const nested = parse('{"child":{"__proto__":{"polluted":true}},"items":[{"__proto__":1}]}'); + expect(Object.prototype.hasOwnProperty.call(nested.child, "__proto__")).toBe(true); + expect(Object.prototype.hasOwnProperty.call(nested.items[0], "__proto__")).toBe(true); + expect(nested.child.__proto__).toEqual(protoValue); + expect(nested.items[0].__proto__).toBe(1); + expect(Object.prototype.polluted).toBeUndefined(); + + const parsedWithFlags = parse('{"__proto__":{"polluted":true},"safe":1}', OBJ | STR); + expect(Object.prototype.hasOwnProperty.call(parsedWithFlags, "__proto__")).toBe(true); + expect(parsedWithFlags.__proto__).toEqual(protoValue); + expect(parsedWithFlags.safe).toBe(1); + expect(Object.prototype.polluted).toBeUndefined(); + expect(() => parse("{", STR)).toThrow(PartialJSON); expect(() => parse('{"', STR)).toThrow(PartialJSON); expect(() => parse('{""', STR)).toThrow(PartialJSON);