# React Server Components, Cooked Again: The Unauthenticated Typed-Array RCE (GHSA-xqc2-xf4c-x3h3)

- URL: https://www.dhruvvakharwala.dev/blogs/react-server-components-typed-array-rce
- Author: Dhruv Vakharwala (https://www.dhruvvakharwala.dev/about)
- Published: November 10, 2026
- Category: Security
- Keywords: react server components rce, ghsa-xqc2-xf4c-x3h3, next.js rce, react-server-dom-webpack, flight protocol, typed array rce, use cache, unauthenticated rce, react security, cache components

> An unauthenticated RCE in React Server Components (GHSA-xqc2-xf4c-x3h3, CVSS 9.5) hits Next.js 16 and react-server-dom-* via typed-array Flight payloads.

**TL;DR:** React Server Components has a second unauthenticated remote code execution bug, tracked as **GHSA-xqc2-xf4c-x3h3** (React) and **GHSA-673q-mjm6-r5xr** (Next.js), both rated **CVSS v4 9.5** and both disclosed on October 14, 2026. There is **no CVE id**, so scanners that read only CVE feeds stay quiet. It affects `react-server-dom-webpack`, `react-server-dom-parcel` and `react-server-dom-turbopack` 19.0.0 through 19.3.0, and **Next.js 16.0.0 through 16.4.0** when an app uses the App Router with Cache Components. The trigger is narrow: a Flight response that carries multiple typed arrays plus an attacker-controlled value, which in Next.js means a `use cache` function that returns multiple typed arrays. Fixed in react-server-dom-* 19.0.9, 19.1.10, 19.2.9, 19.3.1 and in **Next.js 16.4.1**. There is no workaround. Upgrade and redeploy, including preview and branch builds. This post explains the Flight framing bug, how to check whether you are affected without an exploit, and exactly what to upgrade.


![Cover: unauthenticated RCE in React Server Components, GHSA-xqc2-xf4c-x3h3, CVSS 9.5, affected and patched versions](https://www.dhruvvakharwala.dev/images/blog/react-server-components-typed-array-rce/cover.png)

## React is cooked again

Eleven months ago I wrote about [CVE-2025-55182](https://www.dhruvvakharwala.dev/blogs/react-server-components-rce-cve-2025-55182), the first unauthenticated RCE in React Server Components (RSC). That one was an insecure deserialization of the Flight reply: send a crafted request to any Server Function endpoint and the server would execute attacker input, no login required. It scored a perfect 10.0.

On October 14, 2026, the React team shipped another one. Same subsystem, different mechanism. This time the problem is not how the server reads a request, but how it **writes a response**: the part of the Flight protocol that serializes binary data, specifically typed arrays like `Uint8Array`. A crafted response that carries multiple typed arrays alongside an attacker-controlled value can knock the byte stream out of alignment, and from there React says it can reach remote code execution.

It is rated **9.5 on CVSS v4**, it is unauthenticated and network-reachable, and it has **no CVE id** at all. That last detail matters more than it sounds, and I will come back to it.

---

## What exactly is the bug?

There are two advisories for one flaw, published the same day by the React and Next.js teams.

| Field | React advisory | Next.js advisory |
|---|---|---|
| Id | GHSA-xqc2-xf4c-x3h3 | GHSA-673q-mjm6-r5xr |
| Title | Unauthenticated RCE in React Server Components | Unauthenticated RCE in `use cache` functions returning typed arrays |
| CVE | none | none |
| CVSS v4 | 9.5 | 9.5 |
| Vector | AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:N | same |
| Published | 2026-10-14 | 2026-10-14 |
| Finder | evolutionstorm | evolutionstorm |

The CVSS vector reads the way every maintainer dreads: attack vector Network, attack complexity Low, no privileges, no user interaction, high impact on confidentiality, integrity and availability, and it breaks out of its own security scope (`SC:H/SI:H`). The one piece of good news is `AT:P`, "attack requirements: present", the metric that says the target has to be in a particular state for the attack to land. That is the "extremely rare" condition I will get to.

Affected and fixed versions:

| Package | Affected | Fixed |
|---|---|---|
| react-server-dom-webpack | 19.0.0-19.0.8, 19.1.0-19.1.9, 19.2.0-19.2.8, 19.3.0 | 19.0.9, 19.1.10, 19.2.9, 19.3.1 |
| react-server-dom-parcel | same | same |
| react-server-dom-turbopack | same | same |
| next (App Router + Cache Components) | 16.0.0-16.4.0 | 16.4.1 |

The React advisory spells out when you are **not** affected: if your React code does not use a server, or you use no framework, bundler, or bundler plugin that supports React Server Components. A plain client-side React app is safe. The bug lives entirely on the server rendering path.

---

## The root cause: a length header that lies

React has not published the exact chain from the protocol bug to code execution, and as of writing nobody has published a working reproduction of the execution step. What the patch does make clear is the **framing bug** it removes, and that is worth understanding because it is a clean example of a whole class of parser desync vulnerabilities.

RSC talks to itself over a wire format called **Flight**. A server component tree is serialized into a stream of numbered rows, sent to the client (or to the next stage of rendering), and reassembled. Binary values, the typed arrays, are written as length-prefixed chunks: a header that says "the next N bytes are this typed array", then N bytes.

Two things went wrong.

**1. Streams and part ids shared a namespace.** When a stream was serialized, its chunks were written under the same form-field keys that ordinary part ids use. A stream reference and a part reference could therefore collide and be confused for each other. The patch gives streams their own namespace, so, in the maintainers' words, the two can no longer collide.

**2. A detached buffer broke the binary framing.** This is the interesting one. Several typed arrays can be **views over a single `ArrayBuffer`**. If that backing buffer is detached partway through writing the response, every view on it suddenly reports a length of zero. The server skipped those now-empty chunks, but it had **already written the length headers** that described them. So the stream announced bytes it never sent.

![Diagram: a length header says 64 bytes, but the detached buffer sent 0, so the reader reads the next field's bytes as the typed array and the stream desyncs](https://www.dhruvvakharwala.dev/images/blog/react-server-components-typed-array-rce/desync.png)

Walk the diagram. The server announces chunk B as 64 bytes. B's buffer gets detached, so zero bytes go on the wire, but the "64" header is already out there. The reader trusts the header and reads 64 bytes starting at B's position, which are actually the first 64 bytes of field C, the attacker-controlled value. Now every field after B is read from the wrong offset. The deserializer is parsing attacker bytes as a structured value it thinks it understands.

That is the primitive: an attacker-controlled value gets interpreted as something it is not, because the length the server promised never matched the bytes it sent. The patched server now **throws an error** rather than emitting a chunk for a detached buffer, so the headers and the bytes can never disagree.

I want to be precise about the honesty here, because overclaiming helps nobody: the published material explains the desync, not the jump from desync to arbitrary code execution. React is deliberately not shipping that detail, and this post does not attempt it. The framing bug above is what the fix closes, and it is enough to understand why you must patch.

Here is the shape of the exchange, normal versus desynced:

```mermaid
sequenceDiagram
    participant A as Attacker
    participant S as RSC server (Flight writer)
    participant R as Flight reader
    A->>S: request that yields multiple typed arrays sharing one buffer
    Note over S: buffer detaches mid-write
    S->>R: header "chunk B: 64 bytes"
    S-->>R: 0 bytes actually sent for B
    S->>R: field C: attacker-controlled value
    Note over R: reads 64 bytes for B from C's bytes
    R->>R: every later field parsed at the wrong offset
    Note over R: stream desynced, attacker value misread
```

---

## Why "extremely rare" is not the same as "safe"

Both React and Vercel describe the trigger as extremely rare. The React advisory: it only affects cases where responses transport multiple typed arrays and an attacker-controlled value. The Next.js advisory is more concrete: the application needs a `use cache` function that returns multiple typed arrays.

That rarity is real. Most apps never return raw binary from a cached server function. But rarity is a statement about the average codebase, not about yours, and there are three reasons not to treat it as an all-clear:

- **You often cannot see it from the top.** A `use cache` function can return a value that *contains* typed arrays several layers down, from a helper, a library, or a serialization step you did not write. "We do not do that" is hard to prove.
- **CVSS already priced it in.** The 9.5 score is calculated with "attack requirements: present" set. The maintainers knew the condition is narrow and still rated it critical, because when it does line up the impact is total.
- **There is no workaround.** The Next.js advisory states plainly: there is no known workaround for affected applications with such `use cache` functions. The only fix is the upgrade, and Vercel recommends upgrading even if your app has no such function.

---

## Am I affected? Check it without an exploit

You do not need a proof of concept to answer this. It is a version question, and it has one sharp edge.

```mermaid
flowchart TD
    A["Do you render React Server Components on a server?"] -->|No, client-only React| SAFE["Not affected"]
    A -->|Yes| B{"Using Next.js?"}
    B -->|Yes| C["Check next version"]
    B -->|No, other RSC framework or custom| D["Check react-server-dom-* version"]
    C --> E{"next in 16.0.0 - 16.4.0?"}
    E -->|Yes| V["Affected: upgrade to 16.4.1"]
    E -->|No| F["Confirm the bundled RSC runtime is patched"]
    D --> G{"react-server-dom-* below the patched line?"}
    G -->|Yes| V2["Affected: upgrade the RSC packages"]
    G -->|No| OK["Patched"]
    style V fill:#d05a5a,stroke:#b03a3a,color:#fff
    style V2 fill:#d05a5a,stroke:#b03a3a,color:#fff
    style SAFE fill:#3f9c74,stroke:#2f7c5a,color:#fff
    style OK fill:#3f9c74,stroke:#2f7c5a,color:#fff
```

The sharp edge: **Next.js bundles its own copy of the RSC runtime.** If you check for the React packages in a Next.js project, you get nothing back, while the vulnerable code is sitting in `node_modules/next`. I set up a throwaway project on the vulnerable `next@16.4.0` to show it:

![Terminal: npm ls next shows 16.4.0 affected, npm ls react-server-dom-webpack is empty, but the vulnerable file ships inside next; a version checker marks the samples](https://www.dhruvvakharwala.dev/images/blog/react-server-components-typed-array-rce/terminal.png)

```bash
$ npm ls next
nextapp@1.0.0
`-- next@16.4.0            # AFFECTED (fix is 16.4.1)

$ npm ls react-server-dom-webpack   # looks clean, but Next bundles its own copy
nextapp@1.0.0
`-- (empty)

$ find node_modules/next -name 'react-server-dom-webpack-server*' | head -1
node_modules/next/dist/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-server.edge.production.js
# the vulnerable RSC server runtime ships inside next
```

So the rule is: in a Next.js app, **check `next`, not the React packages.** Next 16.0.0 through 16.4.0 is affected; 16.4.1 is the fix. Bumping `react` and `react-dom` does nothing for the bundled runtime.

If you run RSC outside Next, or on another framework or a custom setup, then you do check the React packages directly. Here is a small classifier that reads a version and marks it against the advisory ranges. It is version math, nothing more: no payload, no server, no exploit.

```js title="check-rsc.mjs"
// Classify a react-server-dom-* or next version against GHSA-xqc2-xf4c-x3h3.
const RSC_LINES = [
  { re: /^19\.0\./, fix: "19.0.9" },
  { re: /^19\.1\./, fix: "19.1.10" },
  { re: /^19\.2\./, fix: "19.2.9" },
  { re: /^19\.3\./, fix: "19.3.1" },
];
const atLeast = (a, b) => a.localeCompare(b, undefined, { numeric: true }) >= 0;

export function classifyRSC(v) {
  const line = RSC_LINES.find((l) => l.re.test(v));
  if (!line) return "out of range (not a 19.0-19.3 RSC release)";
  return atLeast(v, line.fix) ? "PATCHED" : `AFFECTED (fix ${line.fix})`;
}
export function classifyNext(v) {
  if (!atLeast(v, "16.0.0")) return "out of scope (advisory covers 16.0.0-16.4.0)";
  return atLeast(v, "16.4.1") ? "PATCHED" : "AFFECTED (fix 16.4.1)";
}
```

Run it across every service and image, because the real risk is the one lockfile you forgot. Three more detection notes, all learned the hard way on bugs like this:

- **Do not trust `npm audit` or a CVE feed.** There is no CVE id, and the advisory took days to propagate to the GitHub advisory database. A clean `npm audit` means nothing here. Match on the GHSA ids directly.
- **Preview and branch deployments count.** A fixed lockfile does not change a build that is already running. Redeploy or tear down old preview environments.
- **There are no published exploitation indicators.** With no public PoC and no IOCs, behavioral monitoring is your backstop: a Node.js server process that spawns a shell or an unexpected child process is an incident until proven otherwise.

---

## How to fix it

The fix is an upgrade. Pick the row that matches you.

| Setup | Do this |
|---|---|
| Next.js 16 | Upgrade to **16.4.1** or later. Applies even if you do not use `use cache`, since there is no workaround. |
| Other RSC framework or custom server | Upgrade `react-server-dom-webpack` / `-parcel` / `-turbopack` to **19.0.9, 19.1.10, 19.2.9 or 19.3.1**, and move `react` and `react-dom` to the matching release. |
| Client-only React | Nothing to do. Not affected. |
| Any preview or branch deploy | Redeploy on the patched version, or delete it. |

The patched releases all exist on the registry today; I confirmed each one resolves:

```bash
$ for v in 19.0.9 19.1.10 19.2.9 19.3.1; do npm view react-server-dom-webpack@$v version; done
19.0.9
19.1.10
19.2.9
19.3.1
$ npm view next@16.4.1 version
16.4.1
```

If you are several minors behind, this is a good moment to jump straight to the latest patch on your line rather than the minimum. The RSC Flight code has been the source of several bugs this year, and being current is the cheapest way to stay ahead of the next one.

---

## The Flight protocol keeps breaking

This is not a one-off. The RSC wire format has produced a steady run of advisories since it shipped, and they rhyme: the serializer and deserializer disagree about what the bytes mean.

| Id | Type | CVSS | Fixed in |
|---|---|---|---|
| CVE-2025-55182 (React2Shell) | Unauthenticated RCE, Flight reply deserialization | 10.0 | react-server-dom-* 19.0.1, 19.1.2, 19.2.1 |
| CVE-2025-55184 | Denial of service | High | 19.0.2, 19.1.3, 19.2.2 |
| CVE-2025-55183 | Source code exposure | Medium | 19.0.2, 19.1.3, 19.2.2 |
| CVE-2026-23864 | Denial of service, unbounded Flight array | High (7.5) | 19.0.4, 19.1.5, 19.2.4 |
| CVE-2026-23869 (React2DoS) | Denial of service, Map/Set re-computation | High (7.5) | 19.2.5 (backports 19.0.5, 19.1.6) |
| GHSA-xqc2-xf4c-x3h3 | Unauthenticated RCE, typed-array framing | 9.5 | 19.0.9, 19.1.10, 19.2.9, 19.3.1 |

The lesson is not "React is insecure". The lesson is that any time you have a custom binary protocol with length-prefixed framing and a deserializer that trusts the headers, a mismatch between what the writer promises and what it sends is a parser's favourite bug. The fix pattern repeats too: make the invalid state impossible to emit. The patched writer refuses to send a detached buffer at all, so the reader never has a chance to trust a lie.

**Warning:** Treat this as a patch-now, not a patch-this-sprint. Upgrade `next` to 16.4.1 across every environment, redeploy preview and branch builds, and do not let a green `npm audit` convince you that you are safe, because there is no CVE for it to match. The trigger is rare, the blast radius is total, and there is no workaround.


## FAQ

### What is GHSA-xqc2-xf4c-x3h3?

It is an unauthenticated remote code execution vulnerability in React Server Components, rated CVSS v4 9.5 and disclosed on October 14, 2026. A crafted Flight response carrying multiple typed arrays plus an attacker-controlled value can desync the byte stream, and React says this can lead to remote code execution. The Next.js counterpart is GHSA-673q-mjm6-r5xr.

### Does this vulnerability have a CVE id?

No. Neither the React advisory nor the Next.js advisory has a CVE assigned, so tools that read only CVE feeds, including `npm audit` in the first days, may not flag it. Match on the GHSA ids instead.

### Which versions are affected and which fix it?

`react-server-dom-webpack`, `react-server-dom-parcel` and `react-server-dom-turbopack` 19.0.0 through 19.3.0 are affected, fixed in 19.0.9, 19.1.10, 19.2.9 and 19.3.1. Next.js 16.0.0 through 16.4.0 is affected when using the App Router with Cache Components, fixed in 16.4.1.

### How do I check if my Next.js app is vulnerable?

Run `npm ls next` and check whether the version is in 16.0.0 through 16.4.0. Do not rely on checking `react-server-dom-webpack`, because Next.js bundles its own copy of the RSC runtime, so that check comes back empty even on a vulnerable app.

### Is there a workaround if I cannot upgrade immediately?

No. The Next.js advisory states there is no known workaround for affected applications with such `use cache` functions. Upgrading to a patched version is the only fix, and you should also redeploy any preview or branch builds that are still running an old version.

### How is this different from CVE-2025-55182?

CVE-2025-55182 (React2Shell) was an insecure deserialization of the incoming Flight reply, triggered by a crafted request to a Server Function, and scored 10.0. This one is on the response side: a framing bug in how the server serializes typed arrays, where a detached buffer makes a length header describe bytes that were never sent.

**Takeaway:** React Server Components has its second unauthenticated RCE, GHSA-xqc2-xf4c-x3h3 at CVSS 9.5, caused by a typed-array framing desync in the Flight protocol, with no CVE to light up your scanners. The trigger is narrow (a response with multiple typed arrays and an attacker value, or a `use cache` function returning multiple typed arrays), but there is no workaround and the impact is complete. Upgrade Next.js to 16.4.1, or react-server-dom-* to 19.0.9 / 19.1.10 / 19.2.9 / 19.3.1, check `next` rather than the React packages, and redeploy every preview build.


## References

- [GHSA-xqc2-xf4c-x3h3: Unauthenticated RCE in React Server Components](https://github.com/react/react/security/advisories/GHSA-xqc2-xf4c-x3h3)
- [GHSA-673q-mjm6-r5xr: Next.js advisory for the same flaw](https://github.com/vercel/next.js/security/advisories/GHSA-673q-mjm6-r5xr)
- [Upwind: what the typed-array RCE means for your Next.js apps](https://www.upwind.io/blog/ghsa-xqc2-xf4c-x3h3-what-the-react-server-components-typed-array-rce-means-for-your-next-js-apps)
- [Endor Labs: critical RCE vulnerabilities in React and Next.js](https://www.endorlabs.com/learn/critical-remote-code-execution-rce-vulnerabilities-in-react-and-next-js)
- [Imperva: React2DoS (CVE-2026-23869) and the Flight protocol](https://www.imperva.com/blog/react2dos-cve-2026-23869-when-the-flight-protocol-crashes-at-takeoff/)
- [My earlier post: reproducing CVE-2025-55182, the first RSC RCE](https://www.dhruvvakharwala.dev/blogs/react-server-components-rce-cve-2025-55182)