From ae181825d520b590dd72740eb10104de643ec2df Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 30 Jan 2026 10:19:47 -0700 Subject: [PATCH] fix(mcp): gate escaped-json unescape fallback --- .../src/components/chat/McpExecution.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/webview-ui/src/components/chat/McpExecution.tsx b/webview-ui/src/components/chat/McpExecution.tsx index 86c02a1888..00d69ec5b9 100644 --- a/webview-ui/src/components/chat/McpExecution.tsx +++ b/webview-ui/src/components/chat/McpExecution.tsx @@ -65,6 +65,20 @@ export const McpExecution = ({ return (trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]")) }, []) + const looksLikeEscapedJsonBlob = useCallback( + (value: string): boolean => { + // Gate the "unescape blob" fallback strictly to avoid mutating arbitrary strings + // that merely contain backslashes. + // Examples we want to handle: + // - `{\"id\":1}` + // - `[{\"id\":1}]` + const trimmed = value.trim() + if (!looksLikeJson(trimmed)) return false + return /^\{\\"/.test(trimmed) || /^\[\s*\{\\"/.test(trimmed) + }, + [looksLikeJson], + ) + const tryParseJsonValue = useCallback((value: string): unknown | undefined => { try { return JSON.parse(value) @@ -108,7 +122,7 @@ export const McpExecution = ({ let parsed: unknown | undefined = tryParseJsonValue(trimmed) // If initial parse fails, try un-escaping common "JSON encoded as a string blob" patterns. - if (parsed === undefined && (trimmed.includes('\\"') || trimmed.includes("\\\\"))) { + if (parsed === undefined && looksLikeEscapedJsonBlob(trimmed)) { const unescaped = tryUnescapeJsonBlob(trimmed) if (unescaped !== undefined) { parsed = tryParseJsonValue(unescaped) @@ -137,7 +151,7 @@ export const McpExecution = ({ formatted: text, } }, - [looksLikeJson, tryParseJsonValue, tryUnescapeJsonBlob], + [looksLikeEscapedJsonBlob, looksLikeJson, tryParseJsonValue, tryUnescapeJsonBlob], ) // Only parse response data when expanded AND complete to avoid parsing partial JSON