You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
π Bug Report β Runtime APIs: formData() corrupts binary file parts with typescript_implemented_streamsΒ #8037
With typescript_implemented_streams, Body.formData() corrupts binary file parts: every byte that is not valid UTF-8 comes back as U+FFFD (EF BF BD). The parsed file is larger than the upload and its contents are wrong. The C++ streams implementation returns the bytes unchanged.
Any Worker that accepts binary uploads through request.formData() is affected. We found it while testing vinext (Next.js on Workers), where every route-handler and Server Action file upload broke. A 100,000-byte upload parsed to 180,948 bytes in a deployed Worker with the flag.
With the flag (workerd 2026-10-27, @cloudflare/workerd-darwin-arm64@1.20261027.1):
sent [0,127,128,255], parsed [0,127,239,191,189,239,191,189]
[ FAIL ] main
With compatibilityFlags = [] (C++ streams):
sent [0,127,128,255], parsed [0,127,128,255]
[ PASS ] main
The same corruption happens for a real multipart request body, and for new Response(arrayBuffer, { headers }).formData(). Reading the same body with arrayBuffer() returns the correct bytes, and FormData serialisation is byte-identical between the two implementations, so only parsing is affected.
Expected behavior
formData() returns file parts byte-for-byte, as it does with the C++ implementation.
Suspected path
Body::formData() (src/workerd/api/http.c++) reads the body with bodyStream.text() and passes the resulting kj::String to FormData::parse(). With the C++ implementation, readAllText() returns the body bytes undecoded. With the TypeScript implementation, JsReadableStream::text() goes through getReadableStreamText() (src/workerd/api/js-readable-stream.c++), which calls consumeReadableStreamAsText. That decodes UTF-8 into a JS string, replacing invalid sequences with U+FFFD, before the string is converted back into a kj::String. Reading the bytes (consumeReadableStreamAsArrayBuffer) for formData() would avoid the decode.
Activity
Fix formData() corrupting binary parts with TypeScript streams