Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,16 @@ const _parseJSON = (jsonString: string, allow: number) => {
index++; // skip colon
try {
const value = parseAny();
obj[key] = value;
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;
Expand Down
26 changes: 26 additions & 0 deletions tests/examples.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,32 @@ 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();
Comment on lines +25 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add more regression cases around __proto__ to cover nested and non-object values and different parse flags.

Right now this only exercises a top-level __proto__ object value with default flags. Please expand coverage with:

  • A case where __proto__ has a primitive value (e.g. {"__proto__":1,"safe":1}) to confirm it’s treated as a normal key and doesn’t affect the prototype.
  • Nested __proto__ keys (inside child objects and array elements) to ensure behavior is consistent beyond the top level.
  • A variant using the same parse flags under which the original bug reproduced, to verify the fix in that configuration.

This will better guard against any remaining prototype-pollution paths.

Suggested change
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();
const protoValue = { polluted: true };
// Top-level __proto__ with object value (default flags)
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();
// Top-level __proto__ with primitive value (default flags)
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();
// Nested __proto__ inside child object (default flags)
const nestedObject = parse('{"level1":{"__proto__":{"polluted":true}},"safe":1}');
expect(Object.prototype.hasOwnProperty.call(nestedObject.level1, "__proto__")).toBe(true);
expect(nestedObject.level1.__proto__).toEqual(protoValue);
expect(nestedObject.safe).toBe(1);
expect(Object.prototype.polluted).toBeUndefined();
// __proto__ inside array elements (default flags)
const arrayNested = parse(
'{"arr":[{"__proto__":{"polluted":true}},{"__proto__":1}],"safe":1}'
);
expect(Object.prototype.hasOwnProperty.call(arrayNested.arr[0], "__proto__")).toBe(true);
expect(Object.prototype.hasOwnProperty.call(arrayNested.arr[1], "__proto__")).toBe(true);
expect(arrayNested.arr[0].__proto__).toEqual(protoValue);
expect(arrayNested.arr[1].__proto__).toBe(1);
expect(arrayNested.safe).toBe(1);
expect(Object.prototype.polluted).toBeUndefined();
// Top-level __proto__ with object value using additional parse flags
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();


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);
Expand Down
Loading