From 2b4ec0bfccd1a3b089267ee102432addecc77c14 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Sun, 21 Jun 2026 03:07:53 +0300 Subject: [PATCH] fix(share-ai): reject non-text message parts to close size-cap bypass (#63) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MAX_SHARE_MESSAGE_CHARS only counted text parts, so a forged non-text part (tool-result/file/data) bypassed the cap and bloated the model input (token-DoS); convertToModelMessages would also expand a forged tool-result. The anonymous path runs no tools, so a client non-text part is never legitimate — reject any message with a non-text part (isTextUIPart) before the size check. Co-Authored-By: Claude Opus 4.8 --- .../src/core/ai-chat/public-share-chat.controller.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/server/src/core/ai-chat/public-share-chat.controller.ts b/apps/server/src/core/ai-chat/public-share-chat.controller.ts index 4c8d0a39..726b6487 100644 --- a/apps/server/src/core/ai-chat/public-share-chat.controller.ts +++ b/apps/server/src/core/ai-chat/public-share-chat.controller.ts @@ -31,7 +31,7 @@ import { } from './public-share-chat.service'; import { evaluateShareAssistantFunnel } from './public-share-chat.funnel'; import { deriveShareAccess } from './public-share-chat.access'; -import type { UIMessage } from 'ai'; +import { isTextUIPart, type UIMessage } from 'ai'; /** * Anonymous, read-only AI assistant over a SINGLE public share tree. @@ -281,6 +281,15 @@ export async function resolveShareAssistantRequest( throw new HttpException('Too many messages', 413); } for (const m of messages) { + const parts = Array.isArray(m?.parts) ? m.parts : []; + // The server runs no tools on the anonymous path, so a client tool/non-text + // part is never legitimate. Reject before the size check: it keeps the char + // cap meaningful (a forged tool-result/file/data part would otherwise bypass + // it and bloat the model input) and avoids stringifying an attacker-sized + // payload via convertToModelMessages. + if (parts.some((p) => !isTextUIPart(p))) { + throw new HttpException('Unsupported message content', 400); + } if (uiMessageTextLength(m) > MAX_SHARE_MESSAGE_CHARS) { throw new HttpException('Message too long', 413); }