diff --git a/deno/lib/README.md b/deno/lib/README.md index a62aaba6a..081790293 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -623,7 +623,7 @@ z.coerce.boolean().parse(null); // => false ## Literals -Literal schemas represent a [literal type](https://www.typescriptlang.org/docs/handbook/literal-types.html), like `"hello world"` or `5`. +Literal schemas represent a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types), like `"hello world"` or `5`. ```ts const tuna = z.literal("tuna"); diff --git a/deno/lib/__tests__/record.test.ts b/deno/lib/__tests__/record.test.ts index b79d97c20..ecb698af9 100644 --- a/deno/lib/__tests__/record.test.ts +++ b/deno/lib/__tests__/record.test.ts @@ -134,3 +134,40 @@ test("key and value getters", () => { rec.valueSchema.parse(1234); rec.element.parse(1234); }); + +test("is not vulnerable to prototype pollution", async () => { + const rec = z.record( + z.object({ + a: z.string(), + }) + ); + + const data = JSON.parse(` + { + "__proto__": { + "a": "evil" + }, + "b": { + "a": "good" + } + } + `); + + const obj1 = rec.parse(data); + expect(obj1.a).toBeUndefined(); + + const obj2 = rec.safeParse(data); + expect(obj2.success).toBe(true); + if (obj2.success) { + expect(obj2.data.a).toBeUndefined(); + } + + const obj3 = await rec.parseAsync(data); + expect(obj3.a).toBeUndefined(); + + const obj4 = await rec.safeParseAsync(data); + expect(obj4.success).toBe(true); + if (obj4.success) { + expect(obj4.data.a).toBeUndefined(); + } +}); diff --git a/deno/lib/helpers/parseUtil.ts b/deno/lib/helpers/parseUtil.ts index 68e7df6bc..a4311a972 100644 --- a/deno/lib/helpers/parseUtil.ts +++ b/deno/lib/helpers/parseUtil.ts @@ -136,7 +136,10 @@ export class ParseStatus { if (key.status === "dirty") status.dirty(); if (value.status === "dirty") status.dirty(); - if (typeof value.value !== "undefined" || pair.alwaysSet) { + if ( + key.value !== "__proto__" && + (typeof value.value !== "undefined" || pair.alwaysSet) + ) { finalObject[key.value] = value.value; } } diff --git a/src/__tests__/record.test.ts b/src/__tests__/record.test.ts index c3b744e73..7a773c748 100644 --- a/src/__tests__/record.test.ts +++ b/src/__tests__/record.test.ts @@ -133,3 +133,40 @@ test("key and value getters", () => { rec.valueSchema.parse(1234); rec.element.parse(1234); }); + +test("is not vulnerable to prototype pollution", async () => { + const rec = z.record( + z.object({ + a: z.string(), + }) + ); + + const data = JSON.parse(` + { + "__proto__": { + "a": "evil" + }, + "b": { + "a": "good" + } + } + `); + + const obj1 = rec.parse(data); + expect(obj1.a).toBeUndefined(); + + const obj2 = rec.safeParse(data); + expect(obj2.success).toBe(true); + if (obj2.success) { + expect(obj2.data.a).toBeUndefined(); + } + + const obj3 = await rec.parseAsync(data); + expect(obj3.a).toBeUndefined(); + + const obj4 = await rec.safeParseAsync(data); + expect(obj4.success).toBe(true); + if (obj4.success) { + expect(obj4.data.a).toBeUndefined(); + } +}); diff --git a/src/helpers/parseUtil.ts b/src/helpers/parseUtil.ts index eb6690ef8..3a278b999 100644 --- a/src/helpers/parseUtil.ts +++ b/src/helpers/parseUtil.ts @@ -136,7 +136,10 @@ export class ParseStatus { if (key.status === "dirty") status.dirty(); if (value.status === "dirty") status.dirty(); - if (typeof value.value !== "undefined" || pair.alwaysSet) { + if ( + key.value !== "__proto__" && + (typeof value.value !== "undefined" || pair.alwaysSet) + ) { finalObject[key.value] = value.value; } }