Skip to content

fix(mime): flush TextDecoder to catch truncated UTF-8 sequences at buffer boundary - #55946

Open
smartworldarafath wants to merge 1 commit into
anomalyco:devfrom
smartworldarafath:fix/mime-utf8-truncated-sequence-detection
Open

smartworldarafath wants to merge 1 commit into
anomalyco:devfrom
smartworldarafath:fix/mime-utf8-truncated-sequence-detection

Conversation

@smartworldarafath

@smartworldarafath smartworldarafath commented Oct 16, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #56183

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

When isBinary decodes a chunk with decoder.decode(chunk, { stream: true }), it tells TextDecoder to expect more chunks. If the chunk ends mid-way through a multi-byte UTF-8 character (e.g. 0xC3 or 0xE2 0x82), TextDecoder buffers the trailing incomplete byte(s) without throwing an error in streaming mode.

Because isBinary immediately finishes its loop without flushing the decoder, truncated multi-byte UTF-8 sequences at chunk boundaries were silently accepted as valid text instead of binary.

This PR adds decoder.decode() to flush any buffered incomplete bytes before returning false, ensuring invalid or truncated trailing multi-byte sequences throw TypeError and correctly classify the input as binary (application/octet-stream).

How did you verify your code works?

  • Tested Mime.detect on buffers with incomplete multi-byte sequences (e.g. truncated 0xC3 / 0xE2 0x82), verifying they are classified as binary.
  • Verified valid ASCII, complete UTF-8, images, and other MIME types remain unaffected.
  • Verified all core MIME and read tests pass cleanly.

Screenshots / recordings

N/A (MIME detection logic)

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

The isText() function in mime.ts was calling TextDecoder.decode() with
{ stream: true } but never calling the flush (a bare .decode() call at
the end). A fatal TextDecoder in streaming mode buffers incomplete
multi-byte sequences silently — the error is only thrown during the
final flush.

This means a file ending with a partial multi-byte sequence (e.g. only
the first byte of a 2-byte or 3-byte UTF-8 codepoint) was incorrectly
classified as 'text/plain' instead of 'application/octet-stream'.

Fix: store the decoder instance, call decode() with stream:true, then
call decode() once more with no arguments to flush and surface any
incomplete sequence at the buffer boundary.
@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct. I ran Mime.detect on both checkouts with your examples: on the base, Hello followed by a cut-off 0xC3 or 0xE2 0x82 is classified as text/plain. With this change it's application/octet-stream, which matches how invalid UTF-8 in the middle of a buffer is already treated. Complete ASCII and UTF-8 input gives the same result as before. The read, read-filesystem and session-prompt tests and the packages/core typecheck pass.

I also checked the callers. The read tool passes only the first 256 KB of a file, but it uses detect only to recognise images and PDFs, so whether that chunk counts as text doesn't matter there. Prompt attachments pass the whole file, so flushing the decoder is correct.

The public API is unchanged.

@github-actions github-actions Bot removed needs:compliance This means the issue will auto-close after 2 hours. needs:issue labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mime.detect treats truncated multi-byte UTF-8 as text/plain

1 participant