From df0aa0d8764234745c3245de678e1335fab94ecd Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Mon, 2 Mar 2026 15:53:56 +0100 Subject: [PATCH 1/2] [Flight Reply] Include Blob size in array nesting limit --- .../src/__tests__/ReactFlightDOMReply-test.js | 38 +++++++++++++++++++ .../src/ReactFlightReplyServer.js | 8 +++- scripts/error-codes/codes.json | 3 +- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js index 77ae692e9800..56b271979f3b 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js @@ -744,4 +744,42 @@ describe('ReactFlightDOMReply', () => { // has closed but that's a bug in both ReactFlightReplyServer and ReactFlightClient. // It just halts in this case. }); + + it('cannot deserialize a Blob reference backed by a string', async () => { + const formData = new FormData(); + formData.set('1', '-'.repeat(50000)); + formData.set('0', JSON.stringify(['$B1'])); + let error; + try { + await ReactServerDOMServer.decodeReply(formData, webpackServerMap); + } catch (x) { + error = x; + } + expect(error.message).toContain('Referenced Blob is not a Blob.'); + }); + + it('cannot deserialize nested arrays with Blob references exceeding the size limit', async () => { + const formData = new FormData(); + formData.set('1', new Blob([new Uint8Array(400_000)])); + const n = 15; + formData.set('2', JSON.stringify(['$B1', '$B1', '$B1'])); + for (let i = 3; i < n; i++) { + formData.set( + String(i), + JSON.stringify([ + '$' + (i - 1).toString(16), + '$' + (i - 1).toString(16), + '$' + (i - 1).toString(16), + ]), + ); + } + formData.set('0', JSON.stringify('$' + (n - 1).toString(16))); + let error; + try { + await ReactServerDOMServer.decodeReply(formData, webpackServerMap); + } catch (x) { + error = x; + } + expect(error.message).toContain('Maximum array nesting exceeded'); + }); }); diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index 21ff08a8aa02..e37d26707392 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -1806,7 +1806,13 @@ function parseModelString( const blobKey = prefix + id; // We should have this backingEntry in the store already because we emitted // it before referencing it. It should be a Blob. - const backingEntry: Blob = (response._formData.get(blobKey): any); + const backingEntry = response._formData.get(blobKey); + if (!(backingEntry instanceof Blob)) { + throw new Error('Referenced Blob is not a Blob.'); + } + if (arrayRoot !== null) { + bumpArrayCount(arrayRoot, backingEntry.size, response); + } return backingEntry; } case 'R': { diff --git a/scripts/error-codes/codes.json b/scripts/error-codes/codes.json index 09e60d8b257b..36cba98f5a9a 100644 --- a/scripts/error-codes/codes.json +++ b/scripts/error-codes/codes.json @@ -566,5 +566,6 @@ "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." + "581": "BigInt is too large. Received %s digits but the limit is %s.", + "582": "Referenced Blob is not a Blob." } From a46c5af68e496fdb7cdffc02a0b567e39dc9a8c4 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Mon, 2 Mar 2026 20:45:43 +0100 Subject: [PATCH 2/2] Bumping the array count is not needed for Blobs --- .../src/__tests__/ReactFlightDOMReply-test.js | 25 ------------------- .../src/ReactFlightReplyServer.js | 3 --- 2 files changed, 28 deletions(-) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js index 56b271979f3b..b569f02390f4 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js @@ -757,29 +757,4 @@ describe('ReactFlightDOMReply', () => { } expect(error.message).toContain('Referenced Blob is not a Blob.'); }); - - it('cannot deserialize nested arrays with Blob references exceeding the size limit', async () => { - const formData = new FormData(); - formData.set('1', new Blob([new Uint8Array(400_000)])); - const n = 15; - formData.set('2', JSON.stringify(['$B1', '$B1', '$B1'])); - for (let i = 3; i < n; i++) { - formData.set( - String(i), - JSON.stringify([ - '$' + (i - 1).toString(16), - '$' + (i - 1).toString(16), - '$' + (i - 1).toString(16), - ]), - ); - } - formData.set('0', JSON.stringify('$' + (n - 1).toString(16))); - let error; - try { - await ReactServerDOMServer.decodeReply(formData, webpackServerMap); - } catch (x) { - error = x; - } - expect(error.message).toContain('Maximum array nesting exceeded'); - }); }); diff --git a/packages/react-server/src/ReactFlightReplyServer.js b/packages/react-server/src/ReactFlightReplyServer.js index e37d26707392..5f58e918f3e8 100644 --- a/packages/react-server/src/ReactFlightReplyServer.js +++ b/packages/react-server/src/ReactFlightReplyServer.js @@ -1810,9 +1810,6 @@ function parseModelString( if (!(backingEntry instanceof Blob)) { throw new Error('Referenced Blob is not a Blob.'); } - if (arrayRoot !== null) { - bumpArrayCount(arrayRoot, backingEntry.size, response); - } return backingEntry; } case 'R': {