VYPR
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.

PackageAffected versionsPatched versions
@zag-js/corenpm
< 0.82.20.82.2

Patches

1
f53edc548f73

fix(security): proto pollution in deepMerge

https://github.com/chakra-ui/zagSegun AdebayoFeb 20, 2025via ghsa
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

5

News mentions

0

No linked articles in our index yet.