Skip to content

Security: replace deepmerge with a prototype-safe merge (10.2.2) - #4

Closed
lane-formio wants to merge 1 commit into
10.2.xfrom
10.2.x-security-patches
Closed

lane-formio wants to merge 1 commit into
10.2.xfrom
10.2.x-security-patches

Conversation

@lane-formio

Copy link
Copy Markdown

Part of the formio-enterprise 9.2.x security patch release. @formio/choices.js 10.2.2 is picked up by formiojs 4.21.8. This PR targets the new 10.2.x maintenance branch, created from v10.2.1.

Why

deepmerge 4.3.1 has a prototype-pollution CVE, CVE-2026-93753 (high), with no fixed release. mergeObject() doesn't validate keys, so a source object carrying __proto__ can inject properties. Choices passes user config and user templates through deepmerge.

Changes

  • New helper: deepMerge() in src/scripts/lib/utils.ts replaces both call sites:
    • config: merge.all([DEFAULT_CONFIG, Choices.defaults.options, userConfig], { arrayMerge: replace })
    • templates: merge(templates, userTemplates)
  • What the helper does:
    • merges plain objects from left to right into a new object, without mutating its inputs
    • replaces arrays with a copy of the source array, which matches the previous arrayMerge for config
    • assigns functions and non-plain objects by reference
    • ignores __proto__, constructor and prototype keys
  • Side effect: a shadowRoot element passed in config is no longer turned into an empty object. The existing restore line is kept.
  • src/scripts/interfaces/choices.ts: imported Options from deepmerge by mistake; it now imports Choices' own ./options type.
  • deepmerge moved to devDependencies: the webpack configs still use it at build time, but it's no longer a runtime dependency, so consumers (formiojs → formio-enterprise) stop installing it.
  • New unit tests for deepMerge: merge semantics, by-reference values and prototype-pollution payloads.

Testing

  • npm run test:unit: 382 passing, including the 3 new deepMerge tests.
  • npm run js:build passes. The rebuilt public/assets/scripts/choices.js has 0 deepmerge references; the currently shipped bundle has 16.
    • Build output isn't committed, following the repo's pattern where the CICD "Updated build" commit regenerates public/.
  • ESLint:
    • no new errors
    • the 2 remaining prettier errors (choices.ts:507, container.ts:64) were already on v10.2.1
    • the type-only import type { Options } from './options' avoids an import/no-cycle error
  • tsc --noEmit: no errors in src/. The 4 errors from conflicting chai type definitions in node_modules (Cypress and @types/chai) are present before and after the change.

🤖 Generated with Claude Code

deepmerge 4.3.1 has a prototype pollution vulnerability (CVE-2026-93753)
with no fixed release. Replace both runtime uses (config and template
merging) with an internal deepMerge() that ignores __proto__,
constructor and prototype keys, and move deepmerge to devDependencies
since only the webpack configs still use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant