@samitouri / QOS-React / commits / 6cb4322d65

[Flight] Port ReplyServer traversal guards to FlightClient (#37144)

Additional defense-in-depth in case consumers pass untrusted input into Flight Client. Flight Client generally assumes trusted input. We'll reserve these kind of fixes for Flight Client in case the untrusted input leads to catastrophic vulnerabilities e.g. prototype pollutions that can be used for remote code executions.

Sebastian "Sebbie" Silbermann committed Jul 29, 2026 at 18:17 UTC 6cb4322d65f4c68daa183df24b5f81668cedab14
1 file changed +26 -8
packages/react-client/src/ReactFlightClient.js
+26 -8
@@ -98,6 +98,8 @@ import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack'
98
99 import hasOwnProperty from 'shared/hasOwnProperty';
100
101 +import getPrototypeOf from 'shared/getPrototypeOf';
102 +
103 import {injectInternals} from './ReactFlightClientDevToolsHook';
104
105 import {OMITTED_PROP_ERROR} from 'shared/ReactFlightPropertyAccess';
@@ -157,6 +159,9 @@ const HALTED = 'halted'; // DEV-only. Means it never resolves even if connection
159
160 const __PROTO__ = '__proto__';
161
162 +const ObjectPrototype = Object.prototype;
163 +const ArrayPrototype = Array.prototype;
164 +
165 type PendingChunk<T> = {
166 status: 'pending',
167 value: null | Array<InitializationReference | (T => mixed)>,
@@ -2170,7 +2175,18 @@ function getOutlinedModel<T>(
2175 }
2176 }
2177 }
2173 - value = value[path[i]];
2178 + const name = path[i];
2179 + if (
2180 + typeof value === 'object' &&
2181 + value !== null &&
2182 + (getPrototypeOf(value) === ObjectPrototype ||
2183 + getPrototypeOf(value) === ArrayPrototype) &&
2184 + hasOwnProperty.call(value, name)
2185 + ) {
2186 + value = value[name];
2187 + } else {
2188 + throw new Error('Invalid reference.');
2189 + }
2190 }
2191
2192 while (
@@ -5382,14 +5398,16 @@ function reviveModel(
5398 }
5399 // Plain object
5400 for (const k in value) {
5385 - if (k === __PROTO__) {
5386 - delete (value as any)[k];
5387 - } else {
5388 - const walked = reviveModel(response, (value as any)[k], value, k);
5389 - if (walked !== undefined) {
5390 - (value as any)[k] = walked;
5391 - } else {
5401 + if (hasOwnProperty.call(value, k)) {
5402 + if (k === __PROTO__) {
5403 delete (value as any)[k];
5404 + } else {
5405 + const walked = reviveModel(response, (value as any)[k], value, k);
5406 + if (walked !== undefined) {
5407 + (value as any)[k] = walked;
5408 + } else {
5409 + delete (value as any)[k];
5410 + }
5411 }
5412 }
5413 }