The Complete Patch: All Three Critical Fixes
Fix #1: Property Traversal in getOutlinedModel() (MOST CRITICAL)
What was broken:
// VULNERABLE CODE
const path = ref.split(':');
for (let i = 1; i < path.length; i++) {
const name = path[i];
value = value[name]; // NO CHECK - Can access __proto__, constructor, etc!
}
The fix:
// PATCHED CODE
const path = ref.split(':');
for (let i = 1; i < path.length; i++) {
const name = path[i];
// CRITICAL FIX: Only access properties the object actually owns
if (typeof value === 'object' && hasOwnProperty.call(value, name)) {
value = value[name];
} else {
return undefined; // Stop if property doesn't exist
}
}
What this line does:
-
hasOwnProperty.call(value, name)- Checks if the property exists on THIS object (not inherited from prototype)
How it blocks the exploit:
-
$1:__proto__:then→ Tries to access__proto__→ BLOCKED (not own property) -
$1:constructor:constructor→ Tries to accessconstructor→ BLOCKED (not own property) - Without prototype access, the entire exploit chain collapses
Fix #2: Module Export Access in requireModule() (Defense-in-Depth)
What was broken:
// VULNERABLE CODE
export function requireModule<T>(metadata: ClientReference<T>): T {
return moduleExports[metadata[NAME]]; // Can access prototype properties
}
The fix:
// PATCHED CODE
if (hasOwnProperty.call(moduleExports, metadata[NAME])) {
return moduleExports[metadata[NAME]];
}
return (undefined: any); // Return undefined for prototype properties
What this does:
- Blocks access to inherited properties on module exports
- Not the main vulnerability, but good defense-in-depth
Fix #3: Error Handling in decodeReplyFromBusboy()
What was broken:
// VULNERABLE CODE
resolveField(response, name, value); // If this throws, server crashes
The fix:
// PATCHED CODE
try {
resolveField(response, name, value);
} catch (error) {
busboyStream.destroy(error); // Safely handle errors
}
What this does:
- Prevents server crashes that could leak information
- Ensures graceful failure during exploitation attempts
Why The Fix Works: Breaking the Exploit Chain
The exploit required all these steps to work:
- Access
__proto__via$1:__proto__:then→ BLOCKED by Fix #1 - Get
Chunk.prototype.then→ BLOCKED by Fix #1 - Trigger second deserialization → Still works, but useless now
- Access Function constructor via
$1:constructor:constructor→ BLOCKED by Fix #1 - Execute malicious code → Can't reach this step anymore
Result: Single hasOwnProperty check breaks the entire chain.
Patched Versions
- React: 19.0.1, 19.1.2, 19.2.1
- Next.js: 15.1.8, 15.1.9, 15.2.4, 15.3.2
Update immediately if you're using React Server Components or Next.js App Router.
Top comments (0)