From 8f22632a29009e7f9efad73a7325897ec0064316 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 09:18:40 -0400 Subject: [PATCH] fix(msteams): validate bot attachment content length --- .../src/attachments/bot-framework.test.ts | 43 +++++++++++++++++++ .../msteams/src/attachments/bot-framework.ts | 14 +++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/extensions/msteams/src/attachments/bot-framework.test.ts b/extensions/msteams/src/attachments/bot-framework.test.ts index feee39d8e29..6e26d323078 100644 --- a/extensions/msteams/src/attachments/bot-framework.test.ts +++ b/extensions/msteams/src/attachments/bot-framework.test.ts @@ -185,6 +185,49 @@ describe("downloadMSTeamsBotFrameworkAttachment", () => { expect(runtime.saveCalls[0].buffer.toString("utf-8")).toBe("PDFBYTES"); }); + it("skips malformed attachment view content-length before saving media", async () => { + const info = { + name: "report.pdf", + type: "application/pdf", + views: [{ viewId: "original", size: 3 }], + }; + const warn = vi.fn(); + const fetchFn = createMockFetch([ + { + match: /\/v3\/attachments\/att-1$/, + response: new Response(JSON.stringify(info), { + status: 200, + headers: { "content-type": "application/json" }, + }), + }, + { + match: /\/v3\/attachments\/att-1\/views\/original$/, + response: new Response("PDFBYTES", { + status: 200, + headers: { "content-length": "0x3" }, + }), + }, + ]); + + const media = await downloadMSTeamsBotFrameworkAttachment({ + serviceUrl: "https://smba.trafficmanager.net/amer/", + attachmentId: "att-1", + tokenProvider: buildTokenProvider(), + maxBytes: 10_000_000, + fetchFn, + fetchFnSupportsDispatcher: true, + resolveFn: resolvePublicHost, + logger: { warn }, + }); + + expect(media).toBeUndefined(); + expect(runtime.saveCalls).toHaveLength(0); + expect(warn).toHaveBeenCalledWith( + "msteams botFramework attachmentView invalid content-length", + { error: "invalid content-length header: 0x3" }, + ); + }); + it("returns undefined when attachment info fetch fails", async () => { const fetchFn = createMockFetch([ { diff --git a/extensions/msteams/src/attachments/bot-framework.ts b/extensions/msteams/src/attachments/bot-framework.ts index 4b8c70bac59..f70a26b3793 100644 --- a/extensions/msteams/src/attachments/bot-framework.ts +++ b/extensions/msteams/src/attachments/bot-framework.ts @@ -1,3 +1,4 @@ +import { parseMediaContentLength } from "openclaw/plugin-sdk/media-runtime"; import { getMSTeamsRuntime } from "../runtime.js"; import { ensureUserAgentHeader } from "../user-agent.js"; import { @@ -164,8 +165,17 @@ async function saveBotFrameworkAttachmentView(params: { }); return undefined; } - const contentLength = response.headers.get("content-length"); - if (contentLength && Number(contentLength) > params.maxBytes) { + let contentLength: number | null; + try { + contentLength = parseMediaContentLength(response.headers.get("content-length")); + } catch (err) { + await response.body?.cancel(); + params.logger?.warn?.("msteams botFramework attachmentView invalid content-length", { + error: err instanceof Error ? err.message : String(err), + }); + return undefined; + } + if (contentLength !== null && contentLength > params.maxBytes) { await response.body?.cancel(); return undefined; }