Mayank Chaudhari
Back to Blog

How React Patched a CVSS 10 Bug: A Lesson in Safe Deserialization

How React Patched a CVSS 10 Bug: A Lesson in Safe Deserialization
Engineering
#React Open Source#Security#Code Analysis#Patch Notes

Complexity is the enemy of security.

The patch for CVE-2025-55182 (React2Shell) wasn't about adding a complex firewall or AI-driven threat detection. It was about simplifying trust.

Let's look at the code.

The Vulnerability Pattern

In the vulnerable version of react-server-dom-webpack, the resolveClientReference function was too permissive. It accepted a map of module IDs and names from the client and attempted to resolve them.

// VULNERABLE (Conceptual)
function resolveClientReference(bundlerConfig, reference) {
  const module = __webpack_require__(reference.id);
  return module[reference.name]; // <--- The trust issue
}

The issue? If reference.name is controlled by the attacker, and module is something sensitive (like the global process object or a module with dangerous getters), accessing that property could trigger side effects or return dangerous objects.

The Patch: Whitelisting

The React team's fix involved enforcing a strict whitelist of allowed exports. You can no longer just ask for "any property" of a module.

The "Diff"

  function resolveClientReference(bundlerConfig, reference) {
    const module = __webpack_require__(reference.id);
-   return module[reference.name];
+   if (!isAllowedExport(module, reference.name)) {
+     throw new Error('Attempted to access disallowed export');
+   }
+   return module[reference.name];
  }

(Note: actual implementation is more complex, involving manifests and type tagging, but this is the logic logic).

The key change is Validation before Access.

Architectural Lesson: Trust Boundaries

The flaw existed because the server treated the "Flight" protocol payload as instructions rather than data.

  • Before: Client says "Run function X from Module Y". Server says "Okay."
  • After: Client says "Run function X". Server says "Is X in my list of public Server Actions? If yes, execute. If no, crash."

This Shift-Left on validation is critical for any library author.

How to Apply This to Your Code

  1. Never accept method names as strings: If your API looks like api.call(methodName, params), you are vulnerable to similar patterns.
  2. Use Maps/Dictionaries: Map string keys to actual function references explicitly.
    const ALLOWED_ACTIONS = {
      'updateUser': updateUser, // Explicit mapping
      'deletePost': deletePost
    };
    // safe
    const action = ALLOWED_ACTIONS[input.actionName];
    
  3. JSON is safer than Custom Parsers: JSON.parse is (mostly) safe because it only produces data. Writing your own parser that instantiates classes is where dangers lie.

Conclusion

The React team reacted swiftly and correctly. But this serves as a reminder: Deserialization logic is the most dangerous code you will ever write. If you can avoid it, do.

Did you enjoy this post?

Give it a like to let me know!

Recommended Posts