fix(share-ai): reject non-text message parts to close size-cap bypass (#63)

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 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-21 03:07:53 +03:00
parent e19849d980
commit 2b4ec0bfcc

View File

@@ -31,7 +31,7 @@ import {
} from './public-share-chat.service'; } from './public-share-chat.service';
import { evaluateShareAssistantFunnel } from './public-share-chat.funnel'; import { evaluateShareAssistantFunnel } from './public-share-chat.funnel';
import { deriveShareAccess } from './public-share-chat.access'; 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. * 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); throw new HttpException('Too many messages', 413);
} }
for (const m of messages) { 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) { if (uiMessageTextLength(m) > MAX_SHARE_MESSAGE_CHARS) {
throw new HttpException('Message too long', 413); throw new HttpException('Message too long', 413);
} }