High severity7.5NVD Advisory· Published Feb 5, 2025· Updated Apr 15, 2026
CVE-2024-57079
CVE-2024-57079
Description
A prototype pollution in the lib.deepMerge function of @zag-js/core v0.50.0 allows attackers to cause a Denial of Service (DoS) via supplying a crafted payload.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@zag-js/corenpm | < 0.82.2 | 0.82.2 |
Patches
1f53edc548f73fix(security): proto pollution in deepMerge
2 files changed · +118 −6
packages/core/src/deep-merge.ts+18 −6 modified@@ -1,16 +1,28 @@ import { compact, isPlainObject } from "@zag-js/utils" export function deepMerge<T extends Record<string, any>>(source: T, ...objects: T[]): T { + if (!isPlainObject(source)) { + throw new TypeError("Source argument must be a plain object") + } + for (const obj of objects) { + if (!isPlainObject(obj)) continue + const target = compact(obj) for (const key in target) { - if (isPlainObject(obj[key])) { - if (!source[key]) { - source[key] = {} as any - } - deepMerge(source[key], obj[key]) + // Skip prototype chain properties + if (!Object.prototype.hasOwnProperty.call(target, key)) continue + + // Skip dangerous prototype pollution keys + if (key === "__proto__" || key === "constructor" || key === "prototype") continue + + const sourceVal = source[key] + const targetVal = obj[key] + + if (isPlainObject(targetVal)) { + source[key] = isPlainObject(sourceVal) ? deepMerge(sourceVal, targetVal) : { ...targetVal } } else { - source[key] = obj[key] + source[key] = targetVal } } }
packages/core/tests/deep-merge.test.ts+100 −0 added@@ -0,0 +1,100 @@ +import { describe, expect, test } from "vitest" +import { deepMerge } from "../src/deep-merge" + +describe("deepMerge", () => { + test("basic object merging", () => { + const obj1 = { a: 1 } + const obj2 = { b: 2 } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: 1, b: 2 }) + }) + + test("deep object merging", () => { + const obj1 = { a: { x: 1 } } + const obj2 = { a: { y: 2 } } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: { x: 1, y: 2 } }) + }) + + test("multiple objects merging", () => { + const obj1 = { a: 1 } + const obj2 = { b: 2 } + const obj3 = { c: 3 } + expect(deepMerge<any>(obj1, obj2, obj3)).toEqual({ a: 1, b: 2, c: 3 }) + }) + + test("overwriting primitives", () => { + const obj1 = { a: 1 } + const obj2 = { a: 2 } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: 2 }) + }) + + test("handles nested object overwriting primitive", () => { + const obj1 = { a: 1 } + const obj2 = { a: { b: 2 } } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: { b: 2 } }) + }) + + // Security Tests + test("prevents prototype pollution", () => { + const malicious = { __proto__: { polluted: true } } + const obj = {} + deepMerge<any>(obj, malicious) + expect(({} as any).polluted).toBeUndefined() + }) + + test("prevents constructor pollution", () => { + const malicious = { constructor: { polluted: true } } + const obj = {} + deepMerge<any>(obj, malicious) + // @ts-expect-error + expect(Object.prototype.polluted).toBeUndefined() + }) + + test("prevents prototype key pollution", () => { + const malicious = { prototype: { polluted: true } } + const obj = {} + deepMerge<any>(obj, malicious) + // @ts-expect-error + expect(Object.prototype.polluted).toBeUndefined() + }) + + // Input Validation Tests + test("throws on non-object source", () => { + expect(() => deepMerge<any>([] as any, {})).toThrow(TypeError) + expect(() => deepMerge<any>(null as any, {})).toThrow(TypeError) + expect(() => deepMerge<any>(42 as any, {})).toThrow(TypeError) + }) + + test("skips non-object arguments", () => { + expect(() => deepMerge<any>({}, [] as any)).not.toThrow() + expect(() => deepMerge<any>({}, null as any)).not.toThrow() + expect(() => deepMerge<any>({}, 42 as any)).not.toThrow() + }) + + // Edge Cases + test("handles empty objects", () => { + expect(deepMerge<any>({}, {})).toEqual({}) + }) + + test("preserves source object when no arguments provided", () => { + const source = { a: 1 } + expect(deepMerge<any>(source)).toEqual({ a: 1 }) + }) + + test("handles nested arrays", () => { + const obj1 = { arr: [1, 2] } + const obj2 = { arr: [3, 4] } + expect(deepMerge<any>(obj1, obj2)).toEqual({ arr: [3, 4] }) + }) + + test("handles null values", () => { + const obj1 = { a: null } + const obj2 = { b: null } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: null, b: null }) + }) + + test("handles undefined values", () => { + const obj1 = { a: undefined } + const obj2 = { b: undefined } + expect(deepMerge<any>(obj1, obj2)).toEqual({ a: undefined, b: undefined }) + }) +})
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5News mentions
0No linked articles in our index yet.