A Flight payload containing circular $ references lands on your server. The deserialiser in ReactFlightReplyServer.js chases those references forever. That’s CVE-2026-23869: a server that stays alive (ports open, handshakes responding) yet cannot answer a single request.
CVSS 7.5, no authentication required.
What makes this interesting isn’t the denial of service itself. It’s that this CVE was disclosed in January 2026, months after the React2Shell RCE patch landed in December 2025. If the critical bug was supposed to be fixed, why were new deserialisation vulnerabilities still surfacing in the same code?
Four CVEs in the same Flight Protocol deserialisation code path across six months is not an incident. It’s a pattern — one that forms a key part of the React2Shell crisis overview. And the pattern tells us something about the architecture that the individual bugs don’t. Start with the most tangible member of the cluster: the one exploit that makes the pattern visible in a single payload.
What Is the “Ouroboros” Cyclic Reference Exploit Pattern in RSC Deserialisation Attacks?
The Flight Protocol uses $ prefixed references (like $1 or $B0) to resolve shared objects and chunk references within a payload. It’s a compression mechanism. An attacker constructs a payload where $1 references an object whose properties reference $1 back, forming what researchers call a Directed Cyclic Graph.
The deserialiser’s resolution algorithm follows these references without tracking visited nodes. That’s the bug.
But the exploit’s destructiveness comes from where it runs. Cyclic Promise references exploit the V8 engine’s microtask scheduling: Promise resolution callbacks are microtasks, and the microtask queue must drain completely before the event loop processes macrotasks like timers and I/O. The cyclic payload creates an unending chain of Promise resolutions. Your server is alive at the network level but brain-dead at the application level.
Penligent’s researchers developed a safe probing technique for this: send a finite-depth cyclic reference and measure latency drift. If response time scales with depth, the server is vulnerable. If it doesn’t, the patch is holding. The technique exists because you can’t safely distinguish a patched server from an unpatched one with a full payload. A full cycle takes the server offline either way.
The payload itself is often under 1KB, sliding past WAFs looking for SQL injection signatures or oversized request bodies. Deserialisation attacks don’t look like attacks.
How Big Is the React Server Components Security Crisis Beyond CVE-2025-55182?
Bigger than most people registered at the time.
The RSC security crisis spans at least seven CVEs beyond React2Shell. CVE-2025-55183 exposes server source code through Server Function stringification (CVSS 5.3). A cluster of five DoS vulnerabilities (CVSS 7.5 each) includes CVE-2025-55184, its incomplete fix CVE-2025-67779, and the follow-ons CVE-2026-23864, CVE-2026-23869, and CVE-2026-23870. CVE-2025-66478 tracked a protocol-level flaw at the Next.js layer, later rejected as a duplicate by NVD. Each targets the same deserialisation codebase through different vectors: Promise resolution, Map/Set iteration, $-reference cycles, and processValue.
As Sonatype’s security team put it at the time: “These issues don’t replace React2Shell; they stack on top of it. While one bug gives you RCE, the others give you ways to knock over servers and leak code, all in the same RSC machinery.” Understanding how a single crafted HTTP request achieves remote code execution requires the full exploit chain in detail.
Then came the May 2026 Vercel coordinated release: 13 advisories in a single batch covering middleware and proxy bypass, SSRF, cache poisoning, and XSS. The deserialisation boundary had extended into the surrounding server infrastructure. SSRF and cache poisoning mean crafted Flight payloads can influence server-side request routing and cache behaviour, well beyond the deserialiser itself.
The version sprawl made patching its own operational problem. Affected ranges spanned React 19.0.x through 19.2.x (patches reaching 19.2.5), Next.js 13.3 through 16.x, and downstream frameworks including React Router, Waku, Parcel RSC, Vite RSC, RedwoodSDK, and Expo. Cloudflare observed 582.1 million React2Shell-related hits in the first eight days after disclosure, averaging 3.49 million per hour. A Metasploit module was published shortly after disclosure, and exploitation attempts were observed within two days.
Why Did the React Team and Vercel Issue Multiple Waves of Advisories Within Months?
The timeline tells the story. November 29, 2025: Lachlan Davidson reports RCE through the Meta Bug Bounty programme. December 3: CVE-2025-55182 disclosed. December 11: CVE-2025-55183 (source exposure) and CVE-2025-55184 (DoS) disclosed, plus CVE-2025-67779 on the same day as an incomplete fix for the DoS. January 2026: CVE-2026-23864, CVE-2026-23869, and CVE-2026-23870. May 2026: Vercel’s 13-advisory batch.
Three dynamics were at play, and likely all three simultaneously. Independent researchers (Andrew MacPherson, RyotaK, Mufeed VH, and others) found different vectors and reported on staggered timelines through the Meta Bug Bounty programme. Each patch revealed adjacent attack surface: the initial DoS fix in CVE-2025-55184 was bypassed within days, producing CVE-2025-67779, which then drew attention to the createMap and createSet functions that became CVE-2026-23869. And the Flight Protocol deserialiser’s architecture is sufficiently intricate that root-cause fixes were harder than incremental, targeted patches.
The React team themselves acknowledged this dynamic: “Security researchers have found and disclosed two additional vulnerabilities in React Server Components while attempting to exploit the patches in last week’s critical vulnerability.” The patch sequence tells the same story: 19.0.1 to 19.0.2 to 19.0.3 to 19.0.4 to 19.0.5, with parallel tracks in 19.1.x and 19.2.x. Even the React team could not anticipate the full vulnerability surface in a single remediation cycle.
What Does the Recurring DoS Pattern in RSC Deserialisation Reveal About the Architecture?
It reveals that the Flight Protocol was designed for trusted payloads and deployed at an untrusted boundary.
The incremental-patch trap is the clearest evidence: CVE-2025-55184 (initial DoS), then CVE-2025-67779 (incomplete fix bypass), then CVE-2026-23864 (additional DoS cases), then CVE-2026-23869 (cyclic deserialisation), then CVE-2026-23870. Each patch closed one vector without addressing the structural weakness. The deserialiser was retrofitted with input validation incrementally rather than architected for an untrusted boundary from the start.
CVE-2026-23869 signals this directly. Why does a production deserialiser shipping in a widely deployed framework lack cycle detection? Because the Flight Protocol’s original design assumed a trusted channel (server-to-server or server-to-client) where cycles would never appear maliciously. As described earlier, the attacker sends a Flight payload with circular $ references and the deserialiser enters unbounded recursion. The unpatched processValue function lacked depth limiting or cycle detection for reference types because, in the design model, those guards were unnecessary.
It’s worth distinguishing the two mechanisms here. CVE-2025-55184 uses Promise-based microtask starvation: cyclic Promise references flood the microtask queue so the event loop never reaches macrotasks. CVE-2026-23869 uses unbounded recursion in createMap, createSet, and extractIterator without necessarily involving Promises. Two attack paths, same architectural root.
As Qualys observed: “React’s server runtime was never built to handle untrusted input. Traditionally, client input is sanitized and filtered by APIs before any server logic is executed. By contrast, React Server Components blurred that separation.”
And here is why that blur exists structurally in RSC.
How Does the RSC Architecture Blur the Traditional Boundary Between Client Input and Server-Side Execution?
Server Components are an architectural innovation: component code stays permanently server-side while accepting structured client payloads. But that innovation eliminates the traditional request/response boundary and replaces it with a serialisation boundary.
Each 'use server' directive creates an HTTP endpoint that accepts Flight Protocol payloads. These payloads are not data. They encode component trees, module references, and object graphs that the server reconstructs and executes. The traditional web-application pattern (parse input, validate, execute) collapses into deserialise-then-execute. That makes the deserialiser the security boundary, whether you designed it to be or not.
The Flight Protocol’s expressiveness is what makes RSC powerful and what makes it dangerous. It’s a row-based streaming format rich enough to represent component trees with lazy-loaded chunks, shared object references, and Promise-based streaming. The $ reference resolution mechanism, designed for efficient wire-format compression, becomes the exploitation primitive for prototype pollution, gadget chains, and cyclic DoS. A format this expressive makes the deserialiser an unusually capable attack surface: it reconstructs attacker-controlled execution contexts, not just data structures.
A standard Next.js application created with create-next-app and built for production is exploitable without any code changes. The vulnerability is present in default configurations. That’s the boundary blur in practice: the framework exposes a deserialisation endpoint by default, and the developer may never know it exists.
How Do RSC Deserialisation Vulnerabilities Compare to Classic Java Deserialisation and Python Pickle Attack Patterns?
The mechanism is the same.
In Java deserialisation, an attacker chains through legitimate classes (Commons Collections, Spring) where method-invocation side effects eventually reach Runtime.exec(). In RSC deserialisation, the attacker chains through Flight $ references: pollute Object.prototype via $1:__proto__:then, rebind _formData.get to the Function constructor via $1:constructor:constructor, inject arbitrary code via _response._prefix. Same pattern: reconstruct attacker-controlled object graphs before validation, exploit legitimate method-chaining side effects to reach dangerous sinks.
Java deserialisation vulnerabilities have been known since at least 2015 with CVE-2015-4852 in Apache Commons Collections, affecting WebSphere, WebLogic, JBoss, and Jenkins. Python’s pickle documentation explicitly warns against unpickling untrusted data. PHP’s unserialize() has produced decades of POP chain vulnerabilities. The RSC vulnerability cluster is not a novel class of attack. It’s a framework-specific instance of OWASP A08 (Insecure Deserialization) that the security community has understood for over a decade.
The question worth asking is why a new serialisation format shipped without the safety mechanisms the broader industry learned were necessary. The Flight Protocol needed to be smarter than JSON, capable of serialising promises, closures, and complex object graphs. That meant it needed to be more complex, more powerful, and more dangerous.
If the pattern is cross-framework, then the takeaway isn’t React-specific.
The React2Shell cluster is the latest chapter in a security pattern the industry has documented in every serialisation format expressive enough to reconstruct object graphs. It happens to have played out in React, but the same pattern has surfaced in Java, Python, PHP, and Ruby before. The Flight Protocol’s trusted-payload assumption, retrofitted incrementally with input validation across six months of advisories, reproduces the architectural mistake that made Java ObjectInputStream, Python pickle, and PHP unserialize() widely documented attack surfaces.
The multi-wave advisory cadence is what happens when a complex deserialisation codebase designed for trust encounters adversarial input for the first time in production — a pattern explored in the complete timeline and defensive response. Each new CVE in the same code path confirms that incremental patches address vectors while leaving the structural assumption intact. The May 2026 Vercel batch demonstrates this boundary is still being mapped. SSRF, cache poisoning, and proxy bypass mean the attack surface extends beyond the deserialiser itself into the surrounding infrastructure. The safe assumption is that it’s larger than what’s been mapped so far.
The vulnerability lies in the decision to deploy a deserialiser designed for co-operating peers at an untrusted Internet-facing boundary — the trust-model failures behind the advisory pattern that made default Next.js deployments exploitable. The implementation flaws follow from that decision. When a framework accepts structured, serialised payloads from clients to drive server-side execution, the deserialiser is the security boundary. Designing it as anything else is the vulnerability.
Frequently Asked Questions
Am I affected if I am running Next.js with the App Router?
Yes, if you are running any Next.js App Router version from 13.3 through to pre-patch 16.x releases without the latest security updates. The App Router uses React Server Components by default, and every CVE in this cluster affects the RSC deserialisation path that the App Router exposes. Check your React version: 19.0.5, 19.1.3, or 19.2.5 (or higher in each line) is the minimum safe state. The May 2026 Vercel coordinated release also requires Next.js patches that address vulnerabilities beyond the React package itself, including middleware bypass and cache poisoning vectors.
What exactly is prototype pollution, and why does it matter for RSC?
Prototype pollution is the ability to modify Object.prototype from attacker controlled input, which then affects every object in the JavaScript runtime. In RSC deserialisation, the Flight Protocol $ reference mechanism lets an attacker set $1:__proto__:then to inject a then function onto Object.prototype. This matters because once Object.prototype is polluted, the attacker can rebind internal framework methods (like _formData.get) to dangerous sinks (like the Function constructor), enabling arbitrary code execution. It turns a data manipulation bug into a full remote code execution chain.
Has my application already been exploited through these vulnerabilities?
You cannot know without reviewing server access logs for unusual Flight Protocol payloads, specifically requests to Server Action endpoints containing $ reference patterns or chunk-encoded payloads with circular reference structures. The Penligent research demonstrated a safe probing technique that sends bounded depth cyclic references to detect vulnerable servers without crashing them. If your application has not been probed, you may have been scanned without incident. The more concerning scenario is that exploitation payloads look identical to legitimate Flight traffic in server logs, making detection of past compromise difficult without specialised analysis.
Can I still use React Server Components safely in production?
Yes, but only after upgrading past the patched version thresholds and applying a defence in depth approach. Patching to React 19.0.5, 19.1.3, or 19.2.5 (or later) addresses all known CVEs at the deserialisation level. Beyond patching, production deployments should add WAF rules that inspect Flight Protocol payloads for unusual $ reference depth, rate limit Server Action endpoints aggressively, and monitor for microtask starvation symptoms (healthy TCP but unresponsive HTTP). The architectural lesson is that the deserialisation boundary must be treated as an untrusted input surface, not as a trusted internal channel.
How do I know if my application has been patched against all the CVEs?
Check your package.json lockfile for the React and Next.js versions against the full affected ranges: React 19.0.0 through 19.0.4 (patch to 19.0.5), 19.1.0 through 19.1.2 (patch to 19.1.3), and 19.2.0 through 19.2.4 (patch to 19.2.5). Next.js requires separate patches for the May 2026 Vercel batch that covers SSRF, cache poisoning, and middleware bypass. Note that CVE-2025-67779 was an incomplete fix for CVE-2025-55184, so applying only the first patch in a version line does not close all known vectors. Run npm audit and cross reference the output against the CVE identifiers listed in the article.
What should I do if I cannot upgrade React immediately?
Deploy a WAF rule set that inspects and blocks Flight Protocol payloads exceeding a safe reference depth (for example, $ reference nesting beyond 20 levels) and restrict Server Action endpoints to authenticated sessions only where possible. Rate limit Server Action endpoints to a handful of requests per second per IP to limit the blast radius of any DoS attempt. Disable Server Actions entirely if your application does not use them: the attack surface exists only where 'use server' directives create publicly reachable HTTP endpoints. These are stopgap measures, not permanent solutions. Upgrade as soon as operationally feasible.
Does this mean Server Components are fundamentally unsafe?
No. The vulnerability cluster reveals a design assumption (trusted payload channel) that was incorrect for Internet facing deployments, not a flaw in the Server Components concept itself. The Flight Protocol wire format is expressive and efficient for its intended purpose. The mistake was shipping it to production without the safety guards (cycle detection, recursion limits, input validation) that every serialisation format handling untrusted input requires. The architecture can be made safe, as the patches demonstrate, but the lesson is that security boundaries must be designed into serialisation formats from the start rather than retrofitted after the fact.
Why was this not caught during React’s development and security review?
The Flight Protocol was designed for a trusted communication model between cooperating peers (server to server, or server to browser under the framework’s own control). In that model, cycle detection, recursion limits, and input validation are unnecessary because payloads originate from code the developer wrote or the framework generated. The security review operated within that trusted scope assumption. The gap appeared when Server Functions exposed those same Flight Protocol endpoints to arbitrary Internet traffic, transforming what was an internal serialisation format into a public attack surface without a corresponding security boundary review. The Meta Bug Bounty programme eventually surfaced what the design assumptions had concealed.
Are there any tools to scan for RSC deserialisation vulnerabilities?
No widely available open source scanners exist yet, though the Penligent research demonstrated a technique. Their bounded recursion probing method sends a finite depth cyclic $ reference payload to a Server Action endpoint and measures latency drift. A vulnerable server shows increasing response time as depth increases, or hangs entirely on a fully cyclic payload. This technique is delicate (test against staging first, never against live production without authorisation) but effective. Expect commercial vulnerability scanners to incorporate RSC specific checks as this vulnerability class matures from novel research into standard detection patterns.
Is this vulnerability cluster unique to React, or should other framework authors be worried?
Other framework authors should be worried. The RSC cluster is the latest confirmation of a cross language pattern: every serialisation format expressive enough to reconstruct complex object graphs (Java ObjectInputStream, Python pickle, PHP unserialize, Ruby Marshal) has been weaponised for deserialisation attacks when deployed at an untrusted boundary. Any framework that accepts structured, serialised component descriptions from clients over a network boundary faces the same architectural question: is the deserialiser designed for untrusted input, or was it built for an internal trusted channel? If the latter, the same vulnerability class will appear under sufficient researcher attention.