[Flight] Add more DoS mitigations to Flight Reply, and harden Flight (#35632)

This fixes security vulnerabilities in Server Functions.

---------

Co-authored-by: Sebastian Markbåge <sebastian@calyptus.eu>
Co-authored-by: Josh Story <josh.c.story@gmail.com>
Co-authored-by: Janka Uryga <lolzatu2@gmail.com>
Co-authored-by: Sebastian Sebbie Silbermann <sebastian.silbermann@vercel.com>
This commit is contained in:
Hendrik Liebau
2026-01-26 20:24:58 +01:00
committed by GitHub
parent 699abc89ce
commit 10680271fa
18 changed files with 835 additions and 263 deletions

View File

@@ -94,6 +94,8 @@ import getComponentNameFromType from 'shared/getComponentNameFromType';
import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
import hasOwnProperty from 'shared/hasOwnProperty';
import {injectInternals} from './ReactFlightClientDevToolsHook';
import {OMITTED_PROP_ERROR} from 'shared/ReactFlightPropertyAccess';
@@ -159,6 +161,8 @@ const INITIALIZED = 'fulfilled';
const ERRORED = 'rejected';
const HALTED = 'halted'; // DEV-only. Means it never resolves even if connection closes.
const __PROTO__ = '__proto__';
type PendingChunk<T> = {
status: 'pending',
value: null | Array<InitializationReference | (T => mixed)>,
@@ -1544,7 +1548,16 @@ function fulfillReference(
}
}
}
value = value[path[i]];
const name = path[i];
if (
typeof value === 'object' &&
value !== null &&
hasOwnProperty.call(value, name)
) {
value = value[name];
} else {
throw new Error('Invalid reference.');
}
}
while (
@@ -1580,7 +1593,9 @@ function fulfillReference(
}
const mappedValue = map(response, value, parentObject, key);
parentObject[key] = mappedValue;
if (key !== __PROTO__) {
parentObject[key] = mappedValue;
}
// If this is the root object for a model reference, where `handler.value`
// is a stale `null`, the resolved value can be used directly.
@@ -1849,7 +1864,9 @@ function loadServerReference<A: Iterable<any>, T>(
response._encodeFormAction,
);
parentObject[key] = resolvedValue;
if (key !== __PROTO__) {
parentObject[key] = resolvedValue;
}
// If this is the root object for a model reference, where `handler.value`
// is a stale `null`, the resolved value can be used directly.
@@ -2231,29 +2248,31 @@ function defineLazyGetter<T>(
): any {
// We don't immediately initialize it even if it's resolved.
// Instead, we wait for the getter to get accessed.
Object.defineProperty(parentObject, key, {
get: function () {
if (chunk.status === RESOLVED_MODEL) {
// If it was now resolved, then we initialize it. This may then discover
// a new set of lazy references that are then asked for eagerly in case
// we get that deep.
initializeModelChunk(chunk);
}
switch (chunk.status) {
case INITIALIZED: {
return chunk.value;
if (key !== __PROTO__) {
Object.defineProperty(parentObject, key, {
get: function () {
if (chunk.status === RESOLVED_MODEL) {
// If it was now resolved, then we initialize it. This may then discover
// a new set of lazy references that are then asked for eagerly in case
// we get that deep.
initializeModelChunk(chunk);
}
case ERRORED:
throw chunk.reason;
}
// Otherwise, we didn't have enough time to load the object before it was
// accessed or the connection closed. So we just log that it was omitted.
// TODO: We should ideally throw here to indicate a difference.
return OMITTED_PROP_ERROR;
},
enumerable: true,
configurable: false,
});
switch (chunk.status) {
case INITIALIZED: {
return chunk.value;
}
case ERRORED:
throw chunk.reason;
}
// Otherwise, we didn't have enough time to load the object before it was
// accessed or the connection closed. So we just log that it was omitted.
// TODO: We should ideally throw here to indicate a difference.
return OMITTED_PROP_ERROR;
},
enumerable: true,
configurable: false,
});
}
return null;
}
@@ -2564,14 +2583,16 @@ function parseModelString(
// In DEV mode we encode omitted objects in logs as a getter that throws
// so that when you try to access it on the client, you know why that
// happened.
Object.defineProperty(parentObject, key, {
get: function () {
// TODO: We should ideally throw here to indicate a difference.
return OMITTED_PROP_ERROR;
},
enumerable: true,
configurable: false,
});
if (key !== __PROTO__) {
Object.defineProperty(parentObject, key, {
get: function () {
// TODO: We should ideally throw here to indicate a difference.
return OMITTED_PROP_ERROR;
},
enumerable: true,
configurable: false,
});
}
return null;
}
// Fallthrough
@@ -5183,6 +5204,9 @@ function parseModel<T>(response: Response, json: UninitializedModel): T {
function createFromJSONCallback(response: Response) {
// $FlowFixMe[missing-this-annot]
return function (key: string, value: JSONValue) {
if (key === __PROTO__) {
return undefined;
}
if (typeof value === 'string') {
// We can't use .bind here because we need the "this" value.
return parseModelString(response, this, key, value);

View File

@@ -95,6 +95,8 @@ export type ReactServerValue =
type ReactServerObject = {+[key: string]: ReactServerValue};
const __PROTO__ = '__proto__';
function serializeByValueID(id: number): string {
return '$' + id.toString(16);
}
@@ -361,6 +363,15 @@ export function processReply(
): ReactJSONValue {
const parent = this;
if (__DEV__) {
if (key === __PROTO__) {
console.error(
'Expected not to serialize an object with own property `__proto__`. When parsed this property will be omitted.%s',
describeObjectForErrorMessage(parent, key),
);
}
}
// Make sure that `parent[key]` wasn't JSONified before `value` was passed to us
if (__DEV__) {
// $FlowFixMe[incompatible-use]
@@ -780,6 +791,10 @@ export function processReply(
if (typeof value === 'function') {
const referenceClosure = knownServerReferences.get(value);
if (referenceClosure !== undefined) {
const existingReference = writtenObjects.get(value);
if (existingReference !== undefined) {
return existingReference;
}
const {id, bound} = referenceClosure;
const referenceClosureJSON = JSON.stringify({id, bound}, resolveToJSON);
if (formData === null) {
@@ -789,7 +804,10 @@ export function processReply(
// The reference to this function came from the same client so we can pass it back.
const refId = nextPartId++;
formData.set(formFieldPrefix + refId, referenceClosureJSON);
return serializeServerReferenceID(refId);
const serverReferenceId = serializeServerReferenceID(refId);
// Store the server reference ID for deduplication.
writtenObjects.set(value, serverReferenceId);
return serverReferenceId;
}
if (temporaryReferences !== undefined && key.indexOf(':') === -1) {
// TODO: If the property name contains a colon, we don't dedupe. Escape instead.

View File

@@ -43,7 +43,7 @@ export function resolveClientReference<T>(
export function resolveServerReference<T>(
config: ServerManifest,
id: ServerReferenceId,
id: mixed,
): ClientReference<T> {
throw new Error(
'renderToHTML should not have emitted Server References. This is a bug in React.',

View File

@@ -328,12 +328,17 @@ function prerenderToNodeStream(
function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
moduleBasePath: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const response = createResponse(
moduleBasePath,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
@@ -399,7 +404,10 @@ function decodeReplyFromBusboy<T>(
function decodeReply<T>(
body: string | FormData,
moduleBasePath: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -411,6 +419,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);

View File

@@ -245,7 +245,10 @@ export function registerServerActions(manifest: ServerManifest) {
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -257,6 +260,7 @@ export function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);

View File

@@ -250,7 +250,10 @@ export function registerServerActions(manifest: ServerManifest) {
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -262,6 +265,7 @@ export function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);

View File

@@ -556,12 +556,17 @@ export function registerServerActions(manifest: ServerManifest) {
export function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const response = createResponse(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
@@ -626,7 +631,10 @@ export function decodeReplyFromBusboy<T>(
export function decodeReply<T>(
body: string | FormData,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -638,6 +646,7 @@ export function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -646,7 +655,10 @@ export function decodeReply<T>(
export function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -655,6 +667,8 @@ export function decodeReplyFromAsyncIterable<T>(
serverManifest,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -239,7 +239,10 @@ function prerender(
function decodeReply<T>(
body: string | FormData,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -251,6 +254,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);

View File

@@ -244,7 +244,10 @@ function prerender(
function decodeReply<T>(
body: string | FormData,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -256,6 +259,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -265,7 +269,10 @@ function decodeReply<T>(
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -274,6 +281,8 @@ function decodeReplyFromAsyncIterable<T>(
turbopackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -548,12 +548,17 @@ function prerender(
function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const response = createResponse(
turbopackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
@@ -619,7 +624,10 @@ function decodeReplyFromBusboy<T>(
function decodeReply<T>(
body: string | FormData,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -631,6 +639,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -640,7 +649,10 @@ function decodeReply<T>(
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
turbopackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -649,6 +661,8 @@ function decodeReplyFromAsyncIterable<T>(
turbopackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -548,12 +548,17 @@ function prerender(
function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const response = createResponse(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
@@ -619,7 +624,10 @@ function decodeReplyFromBusboy<T>(
function decodeReply<T>(
body: string | FormData,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -631,6 +639,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -640,7 +649,10 @@ function decodeReply<T>(
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -649,6 +661,8 @@ function decodeReplyFromAsyncIterable<T>(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -239,7 +239,10 @@ function prerender(
function decodeReply<T>(
body: string | FormData,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -251,6 +254,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);

View File

@@ -244,7 +244,10 @@ function prerender(
function decodeReply<T>(
body: string | FormData,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -256,6 +259,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -265,7 +269,10 @@ function decodeReply<T>(
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -274,6 +281,8 @@ function decodeReplyFromAsyncIterable<T>(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -548,12 +548,17 @@ function prerender(
function decodeReplyFromBusboy<T>(
busboyStream: Busboy,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const response = createResponse(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
let pendingFiles = 0;
const queuedFields: Array<string> = [];
@@ -619,7 +624,10 @@ function decodeReplyFromBusboy<T>(
function decodeReply<T>(
body: string | FormData,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
if (typeof body === 'string') {
const form = new FormData();
@@ -631,6 +639,7 @@ function decodeReply<T>(
'',
options ? options.temporaryReferences : undefined,
body,
options ? options.arraySizeLimit : undefined,
);
const root = getRoot<T>(response);
close(response);
@@ -640,7 +649,10 @@ function decodeReply<T>(
function decodeReplyFromAsyncIterable<T>(
iterable: AsyncIterable<[string, string | File]>,
webpackMap: ServerManifest,
options?: {temporaryReferences?: TemporaryReferenceSet},
options?: {
temporaryReferences?: TemporaryReferenceSet,
arraySizeLimit?: number,
},
): Thenable<T> {
const iterator: AsyncIterator<[string, string | File]> =
iterable[ASYNC_ITERATOR]();
@@ -649,6 +661,8 @@ function decodeReplyFromAsyncIterable<T>(
webpackMap,
'',
options ? options.temporaryReferences : undefined,
undefined,
options ? options.arraySizeLimit : undefined,
);
function progress(

View File

@@ -7,7 +7,7 @@
* @flow
*/
import type {Thenable, ReactFormState} from 'shared/ReactTypes';
import type {ReactFormState} from 'shared/ReactTypes';
import type {
ServerManifest,
@@ -20,26 +20,48 @@ import {
requireModule,
} from 'react-client/src/ReactFlightClientConfig';
import {createResponse, close, getRoot} from './ReactFlightReplyServer';
import {
createResponse,
close,
getRoot,
MAX_BOUND_ARGS,
} from './ReactFlightReplyServer';
type ServerReferenceId = any;
function bindArgs(fn: any, args: any) {
if (args.length > MAX_BOUND_ARGS) {
throw new Error(
'Server Function has too many bound arguments. Received ' +
args.length +
' but the limit is ' +
MAX_BOUND_ARGS +
'.',
);
}
return fn.bind.apply(fn, [null].concat(args));
}
function loadServerReference<T>(
bundlerConfig: ServerManifest,
id: ServerReferenceId,
bound: null | Thenable<Array<any>>,
metaData: {
id: string,
bound: null | Promise<Array<any>>,
},
): Promise<T> {
const id: ServerReferenceId = metaData.id;
if (typeof id !== 'string') {
return (null: any);
}
const serverReference: ServerReference<T> =
resolveServerReference<$FlowFixMe>(bundlerConfig, id);
// We expect most servers to not really need this because you'd just have all
// the relevant modules already loaded but it allows for lazy loading of code
// if needed.
const preloadPromise = preloadModule(serverReference);
if (bound) {
const bound = metaData.bound;
if (bound instanceof Promise) {
return Promise.all([(bound: any), preloadPromise]).then(
([args]: Array<any>) => bindArgs(requireModule(serverReference), args),
);
@@ -57,6 +79,7 @@ function decodeBoundActionMetaData(
body: FormData,
serverManifest: ServerManifest,
formFieldPrefix: string,
arraySizeLimit: void | number,
): {id: ServerReferenceId, bound: null | Promise<Array<any>>} {
// The data for this reference is encoded in multiple fields under this prefix.
const actionResponse = createResponse(
@@ -64,6 +87,7 @@ function decodeBoundActionMetaData(
formFieldPrefix,
undefined,
body,
arraySizeLimit,
);
close(actionResponse);
const refPromise = getRoot<{
@@ -89,6 +113,7 @@ export function decodeAction<T>(
const formData = new FormData();
let action: Promise<(formData: FormData) => T> | null = null;
const seenActions = new Set<string>();
// $FlowFixMe[prop-missing]
body.forEach((value: string | File, key: string) => {
@@ -97,21 +122,36 @@ export function decodeAction<T>(
formData.append(key, value);
return;
}
// Later actions may override earlier actions if a button is used to override the default
// form action.
// Later actions may override earlier actions if a button is used to
// override the default form action. However, we don't expect the same
// action ref field to be sent multiple times in legitimate form data.
if (key.startsWith('$ACTION_REF_')) {
if (seenActions.has(key)) {
return;
}
seenActions.add(key);
const formFieldPrefix = '$ACTION_' + key.slice(12) + ':';
const metaData = decodeBoundActionMetaData(
body,
serverManifest,
formFieldPrefix,
);
action = loadServerReference(serverManifest, metaData.id, metaData.bound);
action = loadServerReference(serverManifest, metaData);
return;
}
// A simple action with no bound arguments may appear twice in the form data
// if a button specifies the same action as the default form action. We only
// load the first one, as they're guaranteed to be identical.
if (key.startsWith('$ACTION_ID_')) {
if (seenActions.has(key)) {
return;
}
seenActions.add(key);
const id = key.slice(11);
action = loadServerReference(serverManifest, id, null);
action = loadServerReference(serverManifest, {
id,
bound: null,
});
return;
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -557,6 +557,8 @@ type DeferredDebugStore = {
existing: Map<ReactClientReference | string, number>,
};
const __PROTO__ = '__proto__';
const OPENING = 10;
const OPEN = 11;
const ABORTING = 12;
@@ -3447,6 +3449,17 @@ function renderModelDestructive(
// Set the currently rendering model
task.model = value;
if (__DEV__) {
if (parentPropertyName === __PROTO__) {
callWithDebugContextInDEV(request, task, () => {
console.error(
'Expected not to serialize an object with own property `__proto__`. When parsed this property will be omitted.%s',
describeObjectForErrorMessage(parent, parentPropertyName),
);
});
}
}
// Special Symbol, that's very common.
if (value === REACT_ELEMENT_TYPE) {
return '$';

View File

@@ -555,5 +555,16 @@
"567": "Already initialized stream.",
"568": "Already initialized typed array.",
"569": "Cannot have cyclic thenables.",
"570": "Invalid reference."
"570": "Invalid reference.",
"571": "Maximum array nesting exceeded. Large nested arrays can be dangerous. Try adding intermediate objects.",
"572": "Already initialized Map.",
"573": "Already initialized Set.",
"574": "Invalid forward reference.",
"575": "Invalid Map initializer.",
"576": "Invalid Set initializer.",
"577": "Invalid Iterator initializer.",
"578": "Already initialized Iterator.",
"579": "Invalid data for bytes stream.",
"580": "Server Function has too many bound arguments. Received %s but the limit is %s.",
"581": "BigInt is too large. Received %s digits but the limit is %s."
}