diff --git a/src/llm/utils/oauth/anthropic.test.ts b/src/llm/utils/oauth/anthropic.test.ts index 3033260b480b..4f9d15d93751 100644 --- a/src/llm/utils/oauth/anthropic.test.ts +++ b/src/llm/utils/oauth/anthropic.test.ts @@ -79,4 +79,26 @@ describe("Anthropic OAuth token responses", () => { "Anthropic token refresh returned invalid token fields.", ); }); + + it("rejects an oversized Anthropic token refresh response", async () => { + let pullCount = 0; + const cancel = vi.fn(async () => undefined); + const oversizedStream = new ReadableStream({ + pull(controller) { + pullCount += 1; + controller.enqueue(new Uint8Array(pullCount === 1 ? 16 * 1024 * 1024 + 1 : 1)); + }, + cancel, + }); + + vi.stubGlobal( + "fetch", + vi.fn(async () => new Response(oversizedStream, { status: 200 })), + ); + + await expect(refreshAnthropicToken("old-refresh-token")).rejects.toThrow("too large"); + + expect(pullCount).toBeLessThanOrEqual(2); + expect(cancel).toHaveBeenCalledOnce(); + }); }); diff --git a/src/llm/utils/oauth/anthropic.ts b/src/llm/utils/oauth/anthropic.ts index 5c98c9f8cc3a..3907c6da788a 100644 --- a/src/llm/utils/oauth/anthropic.ts +++ b/src/llm/utils/oauth/anthropic.ts @@ -6,6 +6,7 @@ */ import type { Server } from "node:http"; +import { readResponseWithLimit } from "@openclaw/media-core/read-response-with-limit"; import { toErrorObject } from "../../../infra/errors.js"; import { generateOAuthState, @@ -52,6 +53,10 @@ const CALLBACK_PATH = "/callback"; const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`; const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload"; + +/** Max response body bytes for Anthropic OAuth token endpoint (16 MiB). */ +const OAUTH_RESPONSE_MAX_BYTES = 16 * 1024 * 1024; + async function getNodeApis(): Promise { if (nodeApis) { return nodeApis; @@ -233,7 +238,10 @@ async function postJson( signal: buildOAuthRequestSignal({ signal: options.signal, timeoutMs }), }); - const responseBody = await response.text(); + const buffer = await readResponseWithLimit(response, OAUTH_RESPONSE_MAX_BYTES, { + onOverflow: ({ size }) => new Error(`Anthropic OAuth response too large: ${size} bytes`), + }); + const responseBody = new TextDecoder().decode(buffer); if (!response.ok) { throw new Error(