From 24e826d06bf7d59a26667df01d93248291f850c7 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Sun, 30 Aug 2026 22:58:58 -0500 Subject: [PATCH] fix(ai): validate Bedrock media data (#46333) --- .../ai/src/protocols/utils/bedrock-media.ts | 18 +++++--- .../ai/test/provider/bedrock-converse.test.ts | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/protocols/utils/bedrock-media.ts b/packages/ai/src/protocols/utils/bedrock-media.ts index 6de2385d9b9..d2a6ab64311 100644 --- a/packages/ai/src/protocols/utils/bedrock-media.ts +++ b/packages/ai/src/protocols/utils/bedrock-media.ts @@ -1,4 +1,4 @@ -import { Effect, Schema } from "effect" +import { Effect, Encoding, Schema } from "effect" import type { MediaPart } from "../../schema/index.js" import { ProviderShared } from "../shared.js" @@ -57,6 +57,16 @@ const documentBlock = (name: string, format: DocumentFormat, bytes: string): Doc }, }) +const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part: MediaPart) { + const media = ProviderShared.normalizeMedia(part) + const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe( + Effect.mapError((cause) => + ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause), + ), + ) + return Encoding.encodeBase64(bytes) +}) + // Route by MIME. Known image/document formats lower into a typed block; anything // else fails with a clear error instead of silently degrading to a malformed // document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`) @@ -66,8 +76,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart) const mime = part.mediaType.toLowerCase() const imageFormat = IMAGE_FORMATS[mime as keyof typeof IMAGE_FORMATS] if (imageFormat) { - const media = ProviderShared.normalizeMedia(part) - return { image: { format: imageFormat, source: { bytes: media.base64 } } } satisfies ImageBlock + return { image: { format: imageFormat, source: { bytes: yield* mediaBase64(part) } } } satisfies ImageBlock } if (mime.startsWith("image/")) return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support image media type ${part.mediaType}`) @@ -75,8 +84,7 @@ export const lower = Effect.fn("BedrockMedia.lower")(function* (part: MediaPart) if (documentFormat) { if (!part.filename) return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename") - const media = ProviderShared.normalizeMedia(part) - return documentBlock(part.filename, documentFormat, media.base64) + return documentBlock(part.filename, documentFormat, yield* mediaBase64(part)) } return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`) }) diff --git a/packages/ai/test/provider/bedrock-converse.test.ts b/packages/ai/test/provider/bedrock-converse.test.ts index dfaca9f6f26..39b195289d5 100644 --- a/packages/ai/test/provider/bedrock-converse.test.ts +++ b/packages/ai/test/provider/bedrock-converse.test.ts @@ -1429,6 +1429,20 @@ describe("Bedrock Converse route", () => { }), ) + it.effect("rejects image media that is not valid base64", () => + Effect.gen(function* () { + const error = yield* compileRequest( + LLM.request({ + model, + messages: [Message.user({ type: "media", mediaType: "image/png", data: "https://example.test/image.png" })], + }), + ).pipe(Effect.flip) + + expect(error).toMatchObject({ reason: { _tag: "InvalidRequest" } }) + expect(error.message).toContain("Bedrock Converse media data must be valid base64") + }), + ) + it.effect("lowers document media into Bedrock document blocks with format and name", () => Effect.gen(function* () { const prepared = yield* compileRequest( @@ -1552,6 +1566,37 @@ describe("Bedrock Converse route", () => { }), ) + it.effect("rejects remote media URLs in tool results", () => + Effect.gen(function* () { + const error = yield* compileRequest( + LLM.request({ + model, + messages: [ + Message.assistant([ToolCallPart.make({ id: "call_1", name: "read", input: {} })]), + Message.tool({ + id: "call_1", + name: "read", + result: { + type: "content", + value: [ + { + type: "file", + uri: "https://example.test/report.pdf", + mime: "application/pdf", + name: "report.pdf", + }, + ], + }, + }), + ], + }), + ).pipe(Effect.flip) + + expect(error).toMatchObject({ reason: { _tag: "InvalidRequest" } }) + expect(error.message).toContain("Bedrock Converse media data must be valid base64") + }), + ) + it.effect("rejects unsupported image media types", () => Effect.gen(function* () { const error = yield* compileRequest(