Skip to content

fix: prevent stack overflow in toBase64 for large inputs in non-Node environments#963

Open
claygeo wants to merge 1 commit intoanthropics:mainfrom
claygeo:fix/base64-stack-overflow
Open

fix: prevent stack overflow in toBase64 for large inputs in non-Node environments#963
claygeo wants to merge 1 commit intoanthropics:mainfrom
claygeo:fix/base64-stack-overflow

Conversation

@claygeo
Copy link
Copy Markdown

@claygeo claygeo commented Mar 27, 2026

Problem

toBase64 in src/internal/utils/base64.ts crashes with RangeError: Maximum call stack size exceeded when encoding inputs larger than ~100KB in non-Node environments (browsers, Cloudflare Workers, Deno, etc.).

Reproduction:

const large = new Uint8Array(200 * 1024); // 200KB
toBase64(large); // RangeError: Maximum call stack size exceeded

This affects any use case that sends large payloads through the SDK in browser environments, most commonly image uploads via base64 encoding.

Root Cause

Line 18 uses String.fromCharCode.apply(null, data), which spreads the entire Uint8Array as individual function arguments. JavaScript engines have a maximum argument limit (typically ~65,536), so any input larger than ~64KB overflows the call stack.

Node.js environments are unaffected because they hit the Buffer.from() path on line 10, which has no such limitation.

Solution

Process the Uint8Array in 32KB chunks (0x8000 bytes), convert each chunk separately with String.fromCharCode.apply(), then join before passing to btoa(). This stays well within the argument limit while producing identical output.

const CHUNK_SIZE = 0x8000;
const parts: string[] = [];
for (let i = 0; i < data.length; i += CHUNK_SIZE) {
  const chunk = data.subarray(i, i + CHUNK_SIZE);
  parts.push(String.fromCharCode.apply(null, chunk as any));
}
return btoa(parts.join(''));

This is a well-established pattern used by libraries like js-base64, Firebase SDK, and others that need to encode large binary data in browser environments.

Note on Generated Code

This file is marked as generated by Stainless. The fix should ideally be upstreamed to the code generator to persist across regenerations. In the meantime, this patch fixes the bug for current users.

Test Plan

  • toBase64(new Uint8Array(0)) → empty string ✅
  • toBase64(new Uint8Array(100)) → valid base64 (small input, existing behavior) ✅
  • toBase64(new Uint8Array(200 * 1024)) → valid base64 without stack overflow ✅
  • toBase64("hello") → string path unaffected ✅
  • Node.js environment → uses Buffer path, unaffected ✅

Closes #932

…environments

String.fromCharCode.apply(null, data) passes the entire Uint8Array as
function arguments, which exceeds the JS engine's max argument limit
(~65536) for inputs larger than ~100KB. This causes a "Maximum call
stack size exceeded" RangeError in browsers and Cloudflare Workers
when encoding large payloads (e.g. image uploads).

The fix processes the Uint8Array in 32KB chunks before joining and
encoding, which stays well within the argument limit while preserving
identical output.

Node.js environments are unaffected as they use Buffer.from() which
has no such limitation.

Closes anthropics#932
@claygeo claygeo requested a review from a team as a code owner March 27, 2026 15:04
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.

toBase64 stack overflow on large inputs (e.g. image uploads) in non-Node environments

1 participant