[Flight Reply] Early bailout if backing entry for Blob deserialization is not a Blob (#36055)
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
Sebastian "Sebbie" Silbermann committed
Mar 17, 2026 at 11:50 UTC
12ba7d81297abac61012a36f20d4a9d22b9210d9
3 files changed
+19
-2
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMReply-test.js
+13
@@ -744,4 +744,17 @@ describe('ReactFlightDOMReply', () => {
744
// has closed but that's a bug in both ReactFlightReplyServer and ReactFlightClient.
745
// It just halts in this case.
746
});
747
+
748
+ it('cannot deserialize a Blob reference backed by a string', async () => {
749
+ const formData = new FormData();
750
+ formData.set('1', '-'.repeat(50000));
751
+ formData.set('0', JSON.stringify(['$B1']));
752
+ let error;
753
+ try {
754
+ await ReactServerDOMServer.decodeReply(formData, webpackServerMap);
755
+ } catch (x) {
756
+ error = x;
757
+ }
758
+ expect(error.message).toContain('Referenced Blob is not a Blob.');
759
+ });
760
});
packages/react-server/src/ReactFlightReplyServer.js
+4
-1
@@ -1806,7 +1806,10 @@ function parseModelString(
1806
const blobKey = prefix + id;
1807
// We should have this backingEntry in the store already because we emitted
1808
// it before referencing it. It should be a Blob.
1809
- const backingEntry: Blob = (response._formData.get(blobKey): any);
1809
+ const backingEntry = response._formData.get(blobKey);
1810
+ if (!(backingEntry instanceof Blob)) {
1811
+ throw new Error('Referenced Blob is not a Blob.');
1812
+ }
1813
return backingEntry;
1814
}
1815
case 'R': {
scripts/error-codes/codes.json
+2
-1
@@ -566,5 +566,6 @@
566
"578": "Already initialized Iterator.",
567
"579": "Invalid data for bytes stream.",
568
"580": "Server Function has too many bound arguments. Received %s but the limit is %s.",
569
- "581": "BigInt is too large. Received %s digits but the limit is %s."
569
+ "581": "BigInt is too large. Received %s digits but the limit is %s.",
570
+ "582": "Referenced Blob is not a Blob."
571
}