diff --git a/src/gateway/chat-attachments.test.ts b/src/gateway/chat-attachments.test.ts new file mode 100644 index 000000000..e07116636 --- /dev/null +++ b/src/gateway/chat-attachments.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; + +import { + buildMessageWithAttachments, + type ChatAttachment, +} from "./chat-attachments.js"; + +const PNG_1x1 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/woAAn8B9FD5fHAAAAAASUVORK5CYII="; + +describe("buildMessageWithAttachments", () => { + it("embeds a single image as data URL", () => { + const msg = buildMessageWithAttachments("see this", [ + { + type: "image", + mimeType: "image/png", + fileName: "dot.png", + content: PNG_1x1, + }, + ]); + expect(msg).toContain("see this"); + expect(msg).toContain(`data:image/png;base64,${PNG_1x1}`); + expect(msg).toContain("![dot.png]"); + }); + + it("rejects non-image mime types", () => { + const bad: ChatAttachment = { + type: "file", + mimeType: "application/pdf", + fileName: "a.pdf", + content: "AAA", + }; + expect(() => buildMessageWithAttachments("x", [bad])).toThrow(/image/); + }); + + it("rejects invalid base64 content", () => { + const bad: ChatAttachment = { + type: "image", + mimeType: "image/png", + fileName: "dot.png", + content: "%not-base64%", + }; + expect(() => buildMessageWithAttachments("x", [bad])).toThrow(/base64/); + }); + + it("rejects images over limit", () => { + const big = Buffer.alloc(6_000_000, 0).toString("base64"); + const att: ChatAttachment = { + type: "image", + mimeType: "image/png", + fileName: "big.png", + content: big, + }; + expect(() => + buildMessageWithAttachments("x", [att], { maxBytes: 5_000_000 }), + ).toThrow(/exceeds size limit/i); + }); +}); diff --git a/src/gateway/chat-attachments.ts b/src/gateway/chat-attachments.ts index 7e7d97834..523da182d 100644 --- a/src/gateway/chat-attachments.ts +++ b/src/gateway/chat-attachments.ts @@ -29,8 +29,13 @@ export function buildMessageWithAttachments( } let sizeBytes = 0; + const b64 = content.trim(); + // Basic base64 sanity: length multiple of 4 and charset check. + if (b64.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(b64)) { + throw new Error(`attachment ${label}: invalid base64 content`); + } try { - sizeBytes = Buffer.from(content, "base64").byteLength; + sizeBytes = Buffer.from(b64, "base64").byteLength; } catch { throw new Error(`attachment ${label}: invalid base64 content`); }