Skip to content
honojs/hono #5366TypeScript

Hono formData() after text() silently corrupts binary file uploads

Since hono#5366, calling formData() after text() silently alters binary uploads. Warpway flagged it in a replay of the PR; we confirmed it and filed #5539.

Published 3 min readReplayed through Warpway on By the Warpway team

Replay vs. what happened

  1. Warpway replay

    Flags it, given only what existed before merge

  2. First report (#5539, filed by us)

    25 days after merge

Flagged: Binary file uploads silently altered when text() is read first

Replayed on 2026-10-07. Warpway was not installed on honojs/hono when this pull request was open.

Hono caches each body representation a handler reads (text(), json(), arrayBuffer(), formData() and so on) and converts between them when the body is read again in a different way. In September 2026, honojs/hono#5366 fixed a gap in that conversion. Rebuilding the body through a bare Response dropped the request's media type, so calling formData() after text() threw. The fix carries the original Content-Type over.

We included this pull request in our backtest as a control: no later fix or revert cites it. When we replayed it through Warpway on 2026-10-07, using only what existed before merge, Warpway flagged it anyway, and the finding turned out to be a real bug that nobody had reported. We reproduced it on the latest release and filed it upstream as honojs/hono#5539.

Key facts

Pull request
honojs/hono#5366
Merged
2026-09-12
Shipped in
4.13.8
Affected
4.13.8 to 4.13.13 (latest)
Language
TypeScript

What Warpway flags in the replay

Warpway reviewReplayed 2026-10-07#5366 fix(request): keep the request media type when reusing a cached body
  • CorrectnessIssue found
  • SecurityIssue found
  • Testing / ReliabilityIssue found
  • Architecture / MaintainabilityIssue found
  • Backward CompatibilityIssue found
  • PerformanceCleared
  • Product SemanticsIssue found
  1. HighArchitecture / Maintainabilitysrc/request.ts:235

    Multipart files are corrupted when formData() follows text()

    For a multipart request containing a file byte such as 0xFF, calling req.text() first decodes the entire body as UTF-8 and replaces that invalid byte with U+FFFD. The new path then rebuilds a Response from that string and the original multipart Content-Type, so req.formData() succeeds but the resulting File contains the UTF-8 bytes EF BF BD rather than the uploaded 0xFF. Calling formData() first reads the original request bytes. This violates the change's stated goal that cached-body reuse behave like parsing the same request directly, and silently changes uploaded data.

Warpway saw the pull request as it stood before merge: final code, title, description and commits, without review comments or later history. Findings are quoted verbatim from the recorded review. Several lenses can report the same issue; Warpway merges them, and the first paragraph is shown.

The change

src/request.ts (4.13.8)
const contentType = anyCachedKey === 'formData' ? undefined : raw.headers.get('content-type');
return new Response(body, {
  headers: contentType ? { 'Content-Type': contentType } : undefined,
})[key]();

How the bug works

When a handler calls c.req.text(), the runtime decodes the whole request body as UTF-8 and Hono caches the resulting string. A multipart upload is not valid UTF-8 when it contains binary file parts, so every invalid byte becomes the replacement character U+FFFD during decoding. That information is gone.

Before #5366, a later formData() call rebuilt a Response from that string without a media type, and the runtime refused to parse it. After #5366, the original multipart/form-data header is carried over, so parsing succeeds, but on the damaged text. The file comes back with EF BF BD (U+FFFD encoded as UTF-8) wherever the upload had a byte such as 0xFF. Nothing throws, so nothing tells you.

Reproduce it

We independently repeated the reproduction with Hono 4.13.13 and Node.js 24 on October 8, 2026. The server returned HTTP 200, with uploaded bytes [255, 0, 65] changed to [239, 191, 189, 0, 65]. The model review identified the scenario through repository investigation; this runtime check separately confirmed the byte transformation.

To repeat the check, use an empty directory with Node.js 24, install hono@4.13.13, save the downloaded script as reproduce.mjs, and run node reproduce.mjs.

js
import { Hono } from 'hono';

const app = new Hono();
app.post('/', async (c) => {
  await c.req.text();
  const file = (await c.req.formData()).get('file');
  return c.json([...new Uint8Array(await file.arrayBuffer())]);
});

const fd = new FormData();
fd.append('file', new Blob([new Uint8Array([0xff, 0x00, 0x41])]), 'x.bin');
console.log(await (await app.request('/', { method: 'POST', body: fd })).text());
Hono versionResult
4.13.7, before #5366formData() throws, and the request fails with a 500
4.13.8 to 4.13.13[239,191,189,0,65]: the request succeeds, but 0xff arrived as EF BF BD

Are you affected?

You are affected on Hono 4.13.8 or later if something reads a multipart request as text before your handler parses the form. Typical cases are middleware that logs or hashes the raw body, and webhook handlers that verify a signature over the body text. Uploads containing only text survive, which is why tests with ASCII form fields pass.

Workaround

Read the raw bytes first and decode them yourself. Hono then rebuilds the form from the original bytes, and file contents are preserved. We verified this on 4.13.13:

js
app.post('/upload', async (c) => {
  const bytes = new Uint8Array(await c.req.arrayBuffer());
  const text = new TextDecoder().decode(bytes); // for logging or signatures
  const form = await c.req.formData(); // file bytes intact
  // ...
});

What happened next

  1. Opened

    #5366 opened

  2. Release

    Hono 4.13.8 ships the change

  3. Warpway replay

    Warpway replay: given the pull request as it stood before merge, one high-severity finding, raised by six lenses

Frequently asked questions

Which Hono versions are affected?

Hono 4.13.8 through 4.13.13, the latest release at the time of writing, and the main branch. Before 4.13.8, the same calls threw a TypeError instead of returning altered bytes.

When does the corruption happen?

Only when a multipart request containing binary data is read with c.req.text() (or json()) first and c.req.formData() afterwards, for example by middleware that reads the raw body. Reading formData() first, or reading only formData(), is not affected.

How do I work around it?

Read the bytes with c.req.arrayBuffer() first, decode them with TextDecoder when you need the text, then call c.req.formData(). Hono then rebuilds the form from the original bytes and file contents are preserved. We verified this on 4.13.13.

Is this a security vulnerability?

We treat it as a data-integrity bug: uploads are silently altered. It is public in the Hono issue tracker as #5539.

How we ran this

  1. 01

    Replay the pull request as it stood before merge

    Its final code, title, description and commits, and the repository at the fork point. No review comments, no bug reports, nothing after merge.

  2. 02

    Review with Warpway’s default lenses

    The production role models, lenses and default policy, run through Codex subscription access. One run per pull request, with no repository configuration.

  3. 03

    Compare with what actually happened

    Later fixes, reverts and bug reports in the public history, linked from every page so you can check our reading.

This pull request is one of 12 we replayed on 2026-10-07 from hono, axios and requests, including ones that were never fixed or reverted and the cases Warpway got wrong. Warpway was not installed on these repositories at the time; every review here is a replay. Older pull requests may appear in the model's training data, which is one reason we include recent ones and a bug nobody had reported. See every result from the pilot.

These replays used Codex subscription access rather than the production OpenAI API. They establish the recorded findings, not production API cost, general accuracy, customer time savings or an approval rate. The projects are open-source examples, not Warpway customers.