VYPR
High severity7.5NVD Advisory· Published Feb 5, 2025· Updated Apr 15, 2026

CVE-2024-57068

CVE-2024-57068

Description

A prototype pollution in the lib.mutateMergeDeep function of @tanstack/form-core v0.35.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
@tanstack/form-corenpm
< 0.42.10.42.1

Patches

1
455522c8f327

fix(form-core): prevent prototype pollution and update Remix dependency - CVE-2024-57068 (#1151)

https://github.com/TanStack/formMoe HajeFeb 20, 2025via ghsa
7 files changed · +801 368
  • docs/reference/functions/mergeform.md+1 1 modified
    @@ -9,7 +9,7 @@ title: mergeForm
     function mergeForm<TFormData, TFormValidator>(baseForm, state): FormApi<NoInfer<TFormData>, NoInfer<TFormValidator>>
     ```
     
    -Defined in: [packages/form-core/src/mergeForm.ts:36](https://github.com/TanStack/form/blob/main/packages/form-core/src/mergeForm.ts#L36)
    +Defined in: [packages/form-core/src/mergeForm.ts:75](https://github.com/TanStack/form/blob/main/packages/form-core/src/mergeForm.ts#L75)
     
     ## Type Parameters
     
    
  • examples/react/remix/package.json+1 1 modified
    @@ -8,7 +8,7 @@
         "_test:types": "tsc"
       },
       "dependencies": {
    -    "@remix-run/node": "^2.15.0",
    +    "@remix-run/node": "^2.15.3",
         "@remix-run/react": "^2.15.0",
         "@remix-run/serve": "^2.15.0",
         "@tanstack/react-form": "^0.42.0",
    
  • packages/form-core/src/mergeForm.ts+58 19 modified
    @@ -2,34 +2,73 @@ import type { FormApi } from './FormApi'
     import type { Validator } from './types'
     import type { NoInfer } from './util-types'
     
    +function isValidKey(key: string | number | symbol): boolean {
    +  const dangerousProps = ['__proto__', 'constructor', 'prototype']
    +  return !dangerousProps.includes(String(key))
    +}
    +
     /**
      * @private
      */
    -export function mutateMergeDeep(target: object, source: object): object {
    +export function mutateMergeDeep(
    +  target: object | null | undefined,
    +  source: object | null | undefined,
    +): object {
    +  // Early return if either is not an object
    +  if (target === null || target === undefined || typeof target !== 'object')
    +    return {} as object
    +  if (source === null || source === undefined || typeof source !== 'object')
    +    return target
    +
       const targetKeys = Object.keys(target)
       const sourceKeys = Object.keys(source)
       const keySet = new Set([...targetKeys, ...sourceKeys])
    +
       for (const key of keySet) {
    -    const targetKey = key as never as keyof typeof target
    -    const sourceKey = key as never as keyof typeof source
    -
    -    if (Array.isArray(target[targetKey]) && Array.isArray(source[sourceKey])) {
    -      // always use the source array to prevent array fields from multiplying
    -      target[targetKey] = source[sourceKey] as [] as never
    -    } else if (
    -      typeof target[targetKey] === 'object' &&
    -      typeof source[sourceKey] === 'object'
    -    ) {
    -      mutateMergeDeep(target[targetKey] as {}, source[sourceKey] as {})
    -    } else {
    -      // Prevent assigning undefined to target, only if undefined is not explicitly set on source
    -      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    -      if (!(sourceKey in source) && source[sourceKey] === undefined) {
    -        continue
    -      }
    -      target[targetKey] = source[sourceKey] as never
    +    if (!isValidKey(key)) continue
    +
    +    const targetKey = key as keyof typeof target
    +    const sourceKey = key as keyof typeof source
    +
    +    if (!Object.hasOwn(source, sourceKey)) continue
    +
    +    const sourceValue = source[sourceKey] as unknown
    +    const targetValue = target[targetKey] as unknown
    +
    +    // Handle arrays
    +    if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
    +      Object.defineProperty(target, key, {
    +        value: [...sourceValue],
    +        enumerable: true,
    +        writable: true,
    +        configurable: true,
    +      })
    +      continue
    +    }
    +
    +    // Handle nested objects (type assertion to satisfy ESLint)
    +    const isTargetObj = typeof targetValue === 'object' && targetValue !== null
    +    const isSourceObj = typeof sourceValue === 'object' && sourceValue !== null
    +    const areObjects =
    +      isTargetObj &&
    +      isSourceObj &&
    +      !Array.isArray(targetValue) &&
    +      !Array.isArray(sourceValue)
    +
    +    if (areObjects) {
    +      mutateMergeDeep(targetValue as object, sourceValue as object)
    +      continue
         }
    +
    +    // Handle all other cases
    +    Object.defineProperty(target, key, {
    +      value: sourceValue,
    +      enumerable: true,
    +      writable: true,
    +      configurable: true,
    +    })
       }
    +
       return target
     }
     
    
  • packages/form-core/tests/mergeForm.spec.ts+100 0 added
    @@ -0,0 +1,100 @@
    +import { describe, expect, it } from 'vitest'
    +import { mutateMergeDeep } from '../src/mergeForm'
    +
    +type TestObject = Record<string, any>
    +
    +describe('mutateMergeDeep', () => {
    +  it('should prevent prototype pollution through __proto__', () => {
    +    const target: TestObject = {}
    +    const malicious = {
    +      __proto__: {
    +        polluted: true,
    +      },
    +    }
    +
    +    mutateMergeDeep(target, malicious)
    +    expect(({} as TestObject).polluted).toBeUndefined()
    +    expect((Object.prototype as TestObject).polluted).toBeUndefined()
    +  })
    +
    +  it('should prevent prototype pollution through constructor', () => {
    +    const target: TestObject = {}
    +    const malicious = {
    +      constructor: {
    +        prototype: {
    +          polluted: true,
    +        },
    +      },
    +    }
    +
    +    mutateMergeDeep(target, malicious)
    +    expect(({} as TestObject).polluted).toBeUndefined()
    +  })
    +
    +  it('should handle null values correctly', () => {
    +    const target = { details: null }
    +    const source = { details: { age: 25 } }
    +
    +    mutateMergeDeep(target, source)
    +    expect(target).toStrictEqual({ details: { age: 25 } })
    +  })
    +
    +  it('should preserve object references when updating nested objects', () => {
    +    const target: { user: { details: TestObject } } = { user: { details: {} } }
    +    const source = { user: { details: { name: 'test' } } }
    +
    +    const originalDetails = target.user.details
    +    mutateMergeDeep(target, source)
    +    expect(target.user.details).toBe(originalDetails)
    +    expect(target.user.details.name).toBe('test')
    +  })
    +
    +  it('Should merge two objects by mutating', () => {
    +    const a = { a: 1 }
    +    const b = { b: 2 }
    +    mutateMergeDeep(a, b)
    +    expect(a).toStrictEqual({ a: 1, b: 2 })
    +  })
    +
    +  it('Should merge two objects including overwriting with undefined', () => {
    +    const a = { a: 1 }
    +    const b = { a: undefined }
    +    mutateMergeDeep(a, b)
    +    expect(a).toStrictEqual({ a: undefined })
    +  })
    +
    +  it('Should merge two object by overriding arrays', () => {
    +    const target = { a: [1] }
    +    const source = { a: [2] }
    +    mutateMergeDeep(target, source)
    +    expect(target).toStrictEqual({ a: [2] })
    +  })
    +
    +  it('Should merge add array element when it does not exist in target', () => {
    +    const target = { a: [] }
    +    const source = { a: [2] }
    +    mutateMergeDeep(target, source)
    +    expect(target).toStrictEqual({ a: [2] })
    +  })
    +
    +  it('Should override the target array if source is undefined', () => {
    +    const target = { a: [2] }
    +    const source = { a: undefined }
    +    mutateMergeDeep(target, source)
    +    expect(target).toStrictEqual({ a: undefined })
    +  })
    +
    +  it('Should merge update array element when it does not exist in source', () => {
    +    const target = { a: [2] }
    +    const source = { a: [] }
    +    mutateMergeDeep(target, source)
    +    expect(target).toStrictEqual({ a: [] })
    +  })
    +
    +  it('Should merge two deeply nested objects', () => {
    +    const a = { a: { a: 1 } }
    +    const b = { a: { b: 2 } }
    +    mutateMergeDeep(a, b)
    +    expect(a).toStrictEqual({ a: { a: 1, b: 2 } })
    +  })
    +})
    
  • packages/form-core/tests/mutateMergeDeep.spec.ts+0 53 removed
    @@ -1,53 +0,0 @@
    -import { describe, expect, test } from 'vitest'
    -import { mutateMergeDeep } from '../src/index'
    -
    -describe('mutateMergeDeep', () => {
    -  test('Should merge two objects by mutating', () => {
    -    const a = { a: 1 }
    -    const b = { b: 2 }
    -    mutateMergeDeep(a, b)
    -    expect(a).toStrictEqual({ a: 1, b: 2 })
    -  })
    -
    -  test('Should merge two objects including overwriting with undefined', () => {
    -    const a = { a: 1 }
    -    const b = { a: undefined }
    -    mutateMergeDeep(a, b)
    -    expect(a).toStrictEqual({ a: undefined })
    -  })
    -
    -  test('Should merge two object by overriding arrays', () => {
    -    const target = { a: [1] }
    -    const source = { a: [2] }
    -    mutateMergeDeep(target, source)
    -    expect(target).toStrictEqual({ a: [2] })
    -  })
    -
    -  test('Should merge add array element when it does not exist in target', () => {
    -    const target = { a: [] }
    -    const source = { a: [2] }
    -    mutateMergeDeep(target, source)
    -    expect(target).toStrictEqual({ a: [2] })
    -  })
    -
    -  test('Should override the target array if source is undefined', () => {
    -    const target = { a: [2] }
    -    const source = { a: undefined }
    -    mutateMergeDeep(target, source)
    -    expect(target).toStrictEqual({ a: undefined })
    -  })
    -
    -  test('Should merge update array element when it does not exist in source', () => {
    -    const target = { a: [2] }
    -    const source = { a: [] }
    -    mutateMergeDeep(target, source)
    -    expect(target).toStrictEqual({ a: [] })
    -  })
    -
    -  test('Should merge two deeply nested objects', () => {
    -    const a = { a: { a: 1 } }
    -    const b = { a: { b: 2 } }
    -    mutateMergeDeep(a, b)
    -    expect(a).toStrictEqual({ a: { a: 1, b: 2 } })
    -  })
    -})
    
  • packages/react-form/package.json+1 1 modified
    @@ -82,7 +82,7 @@
         "src"
       ],
       "dependencies": {
    -    "@remix-run/node": "^2.15.0",
    +    "@remix-run/node": "^2.15.3",
         "@tanstack/form-core": "workspace:*",
         "@tanstack/react-store": "^0.7.0",
         "decode-formdata": "^0.8.0"
    
  • pnpm-lock.yaml+640 293 modified

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.