Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit fcdad69

Browse files
committed
fix(mcp): avoid manual JSON unescape to satisfy CodeQL
1 parent 83b5423 commit fcdad69

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

webview-ui/src/components/chat/McpExecution.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ export const McpExecution = ({
7373
}
7474
}, [])
7575

76+
const tryUnescapeJsonBlob = useCallback((value: string): string | undefined => {
77+
// Some MCP servers return JSON "blobs" with escaped quotes/backslashes but WITHOUT
78+
// wrapping the value in a JSON string literal (e.g. `[{\"id\":1}]`).
79+
//
80+
// Avoid manual `replaceAll("\\\\", "\\")` / `replaceAll('\\"', '"')` transformations,
81+
// which can accidentally double-unescape sequences. Instead, ask the JSON parser to
82+
// interpret escape sequences by wrapping the blob into a JSON string literal.
83+
try {
84+
// Intentionally do NOT escape backslashes, so sequences like `\"` and `\\` are
85+
// interpreted as escapes inside the JSON string literal.
86+
// We only escape raw newlines/tabs so the wrapper remains valid JSON.
87+
const jsonStringLiteral = `"${value
88+
.replaceAll("\n", "\\n")
89+
.replaceAll("\r", "\\r")
90+
.replaceAll("\t", "\\t")}"`
91+
const decoded = JSON.parse(jsonStringLiteral)
92+
return typeof decoded === "string" ? decoded : undefined
93+
} catch {
94+
return undefined
95+
}
96+
}, [])
97+
7698
// Try to parse JSON and return both the result and formatted text.
7799
// Handles:
78100
// - minified JSON
@@ -87,8 +109,10 @@ export const McpExecution = ({
87109

88110
// If initial parse fails, try un-escaping common "JSON encoded as a string blob" patterns.
89111
if (parsed === undefined && (trimmed.includes('\\"') || trimmed.includes("\\\\"))) {
90-
const unescaped = trimmed.replaceAll("\\\\", "\\").replaceAll('\\"', '"')
91-
parsed = tryParseJsonValue(unescaped)
112+
const unescaped = tryUnescapeJsonBlob(trimmed)
113+
if (unescaped !== undefined) {
114+
parsed = tryParseJsonValue(unescaped)
115+
}
92116
}
93117

94118
// If we parsed a string that itself looks like JSON, try parsing again (double-encoded).
@@ -113,7 +137,7 @@ export const McpExecution = ({
113137
formatted: text,
114138
}
115139
},
116-
[looksLikeJson, tryParseJsonValue],
140+
[looksLikeJson, tryParseJsonValue, tryUnescapeJsonBlob],
117141
)
118142

119143
// Only parse response data when expanded AND complete to avoid parsing partial JSON

0 commit comments

Comments
 (0)