Skip to content

[v5.0] fix(provider-utils): bound media-type sniffing decode for ID3-prefixed input - #17746

Merged
lgrammel merged 2 commits into
release-v5.0from
backport-v5.0/17709-2
Jul 22, 2026
Merged

[v5.0] fix(provider-utils): bound media-type sniffing decode for ID3-prefixed input#17746
lgrammel merged 2 commits into
release-v5.0from
backport-v5.0/17709-2

Conversation

@lgrammel

Copy link
Copy Markdown
Collaborator

Summary

Media-type sniffing is documented as an O(1) check on the first ~18 bytes (24 base64 chars). An ID3/SUQz-prefixed attachment bypassed that bound: stripID3TagsIfPresent ran before the truncation and decoded the entire base64 attachment (plus a full-size .slice() copy), then returned a Uint8Array so the 24-char cap was skipped. A 4-character prefix flipped intended O(1) sniffing into an O(N) decode + copy of the whole attachment, on every path including image detection.

Hardening

  • Decode a bounded prefix first, then strip ID3 within that bound, so no input can force a full decode.
  • Detection now runs on decoded bytes (ID3) instead of the base64 string (startsWith('SUQz')), so string and raw-byte inputs are checked uniformly and base64 alignment tricks cannot fool it.
  • The raw-Uint8Array path is bounded too, and stripID3 uses subarray (a view) instead of slice (a full copy), removing the second O(N) copy.
  • ID3 skip is capped at ID3_MAX_SCAN_BYTES (128 KB), large enough to cover typical tags with embedded cover art while keeping cost constant in attachment size.

Happy path

  • Non-ID3 attachments (PNG, JPEG, WEBP, PDF, WAV, and so on) decode only ~18 bytes exactly as before, so common detection is unchanged.
  • MP3s with an ID3v2 tag up to 128 KB, including typical embedded cover art, still auto-detect as audio/mpeg.
  • data: URLs, base64url, ArrayBuffer, Buffer, and text inputs are normalized upstream and behave the same.

Unhappy path

  • An ID3/SUQz-prefixed attachment (benign or crafted) now decodes at most ~128 KB regardless of attachment size, so sniffing stays constant-time instead of scaling with the input.
  • An ID3v2 tag larger than 128 KB is no longer scanned past, so such an MP3 is not auto-detected and falls back to the caller-supplied media type (resolveFullMediaType throws its existing "must specify subtype" error if none was provided). Raise ID3_MAX_SCAN_BYTES if larger tags must be detected; cost stays O(1) either way.
  • A crafted oversized id3Size cannot read out of bounds: subarray clamps to length and returns empty, so no signature matches and detection returns undefined.

Testing

  • Added a regression test that an ID3 tag larger than the scan bound is not detected (locks in the O(1) behavior).
  • Full detect-media-type suite passes (67 tests).

Fixes #17709

Related Issues

Backport of #17386

@lgrammel

Copy link
Copy Markdown
Collaborator Author

Bugfix review

Outcome: approved

Fixes issue

Status: fully-addresses

The implementation limits base64 decoding to a fixed prefix, uses zero-copy Uint8Array subarray views, handles ID3 from decoded bytes, preserves detection at the inclusive 128 KiB boundary, and returns undefined beyond it.

Side effects

Risk: low

Normal non-ID3 signature detection remains limited to the existing 18-byte prefix. The only deliberate behavior change is that oversized ID3 tags are no longer scanned to their trailing media signature.

Performance

Risk: low

ID3 processing now performs attachment-size-independent work: base64 decoding is capped at roughly 128 KiB and raw input is viewed through subarray rather than copied.

Backwards compatibility

Risk: low

The change does not mutate or migrate stored data. It only changes runtime auto-detection for ID3 tags beyond the documented scan limit, where callers can continue supplying an explicit media type.

Architecture

Risk: low

The fix remains localized in the v5 ai package's existing media-detection utility, uses provider-utils through its declared public package export, and introduces no cross-package src imports or inappropriate shared abstractions.

Change scope

Status: minimal

The three changed files are limited to the detection implementation, focused boundary regression tests, and the required patch changeset.

Security

Risk: low

The change reduces denial-of-service exposure from crafted ID3-prefixed attachments and uses clamping subarray operations for malformed or oversized size fields without adding new trust boundaries.

Testing

Status: appropriate

Tests cover raw and base64 MP3 detection at and immediately beyond the 128 KiB boundary. The complete 48-test media-detection suite passed in both Node and edge configurations, and focused verification also covered bounded decoding, zero raw slicing, malformed sizes, combined signatures, and normalized input forms.

Verification

Reviewed every merge-base diff hunk, confirmed clean diff formatting and package boundaries, passed the ai package type-check, passed all 48 media-detection tests in Node and edge environments, and independently verified bounded decoding and raw-copy behavior across boundary, malformed, image, audio, combined, base64url, data URL, ArrayBuffer, Buffer, and text cases.

Relevant Documentation

@lgrammel
lgrammel merged commit 649bd0f into release-v5.0 Jul 22, 2026
57 of 58 checks passed
@lgrammel
lgrammel deleted the backport-v5.0/17709-2 branch July 22, 2026 14:16
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Published in:

Package Version
ai 5.0.219 github npm
@ai-sdk/amazon-bedrock 3.0.109 github npm
@ai-sdk/angular 1.0.219 github npm
@ai-sdk/anthropic 2.0.89 github npm
@ai-sdk/anthropic-aws 0.1.4 github npm
@ai-sdk/gateway 2.0.118 github npm
@ai-sdk/google-vertex 3.0.156 github npm
@ai-sdk/langchain 1.0.219 github npm
@ai-sdk/llamaindex 1.0.219 github npm
@ai-sdk/react 2.0.221 github npm
@ai-sdk/rsc 1.0.221 github npm
@ai-sdk/svelte 3.0.219 github npm
@ai-sdk/vue 2.0.219 github npm

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants