CVE Patch Review

GHSA-MHWJ-73QX-JQXM: Root-cause fix for deepMerge() prototype pollution

GHSA-MHWJ-73QX-JQXM · Updated 2026-05-11 Root-cause

Summary

Version 1.0.1 addresses prototype pollution in deepMerge() by rejecting dangerous property names during recursive merge. The patch directly targets the pollution primitive described in the advisory and expands protection beyond __proto__ to constructor and prototype.

Analysis

Vulnerability

GHSA-MHWJ-73QX-JQXM describes a prototype pollution issue in @theecryptochad/merge-guard v1.0.0. The vulnerable behavior is in deepMerge(), where attacker-controlled object keys are merged without validating special property names. A crafted JSON payload containing __proto__ can mutate Object.prototype, which can in turn alter application behavior globally. The advisory states that the issue is fixed in v1.0.1 by adding a restricted key denylist.

This is a classic unsafe recursive merge pattern: if merge logic traverses arbitrary keys and writes them into target objects, meta-properties such as __proto__, constructor, and prototype can redirect writes into prototype chains rather than ordinary data fields. That makes the root cause insufficient key validation on untrusted input before property assignment.

Patch

The patch in the upstream commit 25e4b4f2618578a656ef3cb4946a1b475f736736 introduces a denylist of dangerous keys and skips them during merge. The package version is also bumped from 1.0.0 to 1.0.1.

const BLOCKED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);

if (BLOCKED_KEYS.has(key)) continue; // CVE fix: block prototype pollution keys

Technically, this changes the merge semantics from accepting all enumerable keys to rejecting known prototype-manipulation primitives. The inclusion of constructor and prototype is important because it broadens coverage beyond the specific __proto__ payload called out in the advisory and addresses common alternate gadget paths used in JavaScript prototype pollution exploitation.

Review

Pros

The patch is tightly aligned with the documented vulnerability in the advisory. It blocks the exact attacker-controlled keys that enable prototype chain mutation in recursive merge code. The implementation is low-risk and minimally invasive: a constant denylist plus an early continue avoids changing unrelated merge behavior. Adding constructor and prototype in addition to __proto__ is a strong engineering choice because it addresses the broader class of prototype pollution entry points rather than only the reported proof-of-concept.

From a maintenance perspective, the patch is easy to audit. The security control is explicit, local to the merge loop, and visible in a single guard. The version bump to 1.0.1 in the same change set also clearly marks the security release boundary.

Cons

The fix uses a denylist, which is effective for the known dangerous keys shown in the sources but is still a narrower strategy than structural hardening. Without the full function context in the provided diff, it is not possible to verify whether all write paths in deepMerge() pass through this guard, whether inherited properties are excluded, or whether object creation semantics avoid prototype-bearing containers entirely. A more defensive design would also consider using objects with null prototypes for merge targets where appropriate, or constraining mergeable input types.

The provided patch summary does not show accompanying regression tests. For a vulnerability in recursive merge logic, tests should cover direct and nested payloads for __proto__, constructor.prototype, and benign keys to confirm both security coverage and compatibility.

Verdict

Root-cause.

Based on the available sources, the patch addresses the underlying flaw: unvalidated special keys were allowed to flow into merge assignments. By rejecting the prototype-pollution primitives at merge time, v1.0.1 removes the dangerous input class that caused the vulnerability. While denylist-based defenses are not the most comprehensive possible hardening pattern, this change is source-grounded, directly mapped to the exploit mechanism, and appears sufficient for the reported issue in the fixing commit and the advisory.

Sources