mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(security): OC-53 validate prompt size before string concatenation to prevent memory exhaustion — Aether AI Agent
This commit is contained in:
committed by
Peter Steinberger
parent
732e53151e
commit
ebcf19746f
@@ -6,25 +6,33 @@ export type GatewayAttachment = {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export function extractTextFromPrompt(prompt: ContentBlock[]): string {
|
||||
export function extractTextFromPrompt(prompt: ContentBlock[], maxBytes?: number): string {
|
||||
const parts: string[] = [];
|
||||
// Track accumulated byte count per block to catch oversized prompts before full concatenation
|
||||
let totalBytes = 0;
|
||||
for (const block of prompt) {
|
||||
let blockText: string | undefined;
|
||||
if (block.type === "text") {
|
||||
parts.push(block.text);
|
||||
continue;
|
||||
}
|
||||
if (block.type === "resource") {
|
||||
blockText = block.text;
|
||||
} else if (block.type === "resource") {
|
||||
const resource = block.resource as { text?: string } | undefined;
|
||||
if (resource?.text) {
|
||||
parts.push(resource.text);
|
||||
blockText = resource.text;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (block.type === "resource_link") {
|
||||
} else if (block.type === "resource_link") {
|
||||
const title = block.title ? ` (${block.title})` : "";
|
||||
const uri = block.uri ?? "";
|
||||
const line = uri ? `[Resource link${title}] ${uri}` : `[Resource link${title}]`;
|
||||
parts.push(line);
|
||||
blockText = uri ? `[Resource link${title}] ${uri}` : `[Resource link${title}]`;
|
||||
}
|
||||
if (blockText !== undefined) {
|
||||
// Guard: reject before allocating the full concatenated string
|
||||
if (maxBytes !== undefined) {
|
||||
totalBytes += Buffer.byteLength(blockText, "utf-8");
|
||||
if (totalBytes > maxBytes) {
|
||||
throw new Error(`Prompt exceeds maximum allowed size of ${maxBytes} bytes`);
|
||||
}
|
||||
}
|
||||
parts.push(blockText);
|
||||
}
|
||||
}
|
||||
return parts.join("\n");
|
||||
|
||||
@@ -264,13 +264,15 @@ export class AcpGatewayAgent implements Agent {
|
||||
this.sessionStore.setActiveRun(params.sessionId, runId, abortController);
|
||||
|
||||
const meta = parseSessionMeta(params._meta);
|
||||
const userText = extractTextFromPrompt(params.prompt);
|
||||
// Pass MAX_PROMPT_BYTES so extractTextFromPrompt rejects oversized content
|
||||
// block-by-block, before the full string is ever assembled in memory (CWE-400)
|
||||
const userText = extractTextFromPrompt(params.prompt, MAX_PROMPT_BYTES);
|
||||
const attachments = extractAttachmentsFromPrompt(params.prompt);
|
||||
const prefixCwd = meta.prefixCwd ?? this.opts.prefixCwd ?? true;
|
||||
const displayCwd = shortenHomePath(session.cwd);
|
||||
const message = prefixCwd ? `[Working directory: ${displayCwd}]\n\n${userText}` : userText;
|
||||
|
||||
// Guard against oversized prompts that could cause memory exhaustion (DoS)
|
||||
// Defense-in-depth: also check the final assembled message (includes cwd prefix)
|
||||
if (Buffer.byteLength(message, "utf-8") > MAX_PROMPT_BYTES) {
|
||||
throw new Error(
|
||||
`Prompt exceeds maximum allowed size of ${MAX_PROMPT_BYTES} bytes`,
|
||||
|
||||
Reference in New Issue
Block a user