From 634ee677d9454e1328d03bc67f5d55154cb91c4b Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Mon, 27 Oct 2025 20:30:25 -0500 Subject: [PATCH] fix(image-uris): resolve review-bot issues ClineProvider.convertToWebviewUri: align JSDoc with non-throwing behavior and fallback to file URI. imageDataUrl.webviewUriToFilePath: remove impossible CDN host check from vscode-resource branch; expand Windows path regex to allow spaces while keeping bounds. ChatTextArea: clear pendingImageUploadsRef via captured ref in cleanup to avoid stale closure. --- src/core/webview/ClineProvider.ts | 8 +++----- src/integrations/misc/imageDataUrl.ts | 20 ++----------------- .../src/components/chat/ChatTextArea.tsx | 9 +++++++++ 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 1e866b2aa9..81de590a53 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2790,13 +2790,11 @@ export class ClineProvider } /** - * Convert a file path to a webview-accessible URI - * This method safely converts file paths to URIs that can be loaded in the webview + * Convert a file path to a webview-accessible URI. + * Safely returns a string and does not throw; logs errors and falls back to a file: URI when conversion is not possible. * * @param filePath - The absolute file path to convert - * @returns The webview URI string, or the original file URI if conversion fails - * @throws {Error} When webview is not available - * @throws {TypeError} When file path is invalid + * @returns The webview URI string when a webview is available; otherwise a file: URI string */ public convertToWebviewUri(filePath: string): string { try { diff --git a/src/integrations/misc/imageDataUrl.ts b/src/integrations/misc/imageDataUrl.ts index bd8f14c14b..b747d665bc 100644 --- a/src/integrations/misc/imageDataUrl.ts +++ b/src/integrations/misc/imageDataUrl.ts @@ -73,10 +73,7 @@ function webviewUriToFilePath(webviewUri: string): string { // Handle VS Code webview URIs that contain encoded paths // Use strict prefix matching to prevent arbitrary host injection - if ( - webviewUri.startsWith("vscode-resource://vscode-webview/") && - (webviewUri.includes("vscode-userdata") || isValidVsCodeCdnHost(webviewUri)) - ) { + if (webviewUri.startsWith("vscode-resource://vscode-webview/") && webviewUri.includes("vscode-userdata")) { try { // Decode safely with length limits if (webviewUri.length > 2048) { @@ -94,7 +91,7 @@ function webviewUriToFilePath(webviewUri: string): string { return unixMatch[0] } - const windowsMatch = decoded.match(/^[^?#]*C:\\[a-zA-Z0-9._\\-]{1,300}\.(png|jpg|jpeg|gif|webp)$/i) + const windowsMatch = decoded.match(/^[^?#]*[A-Za-z]:\\[^?#]{1,300}\.(png|jpg|jpeg|gif|webp)$/i) if (windowsMatch) { return windowsMatch[0] } @@ -110,19 +107,6 @@ function webviewUriToFilePath(webviewUri: string): string { /** * Gets the MIME type from a file path */ -/** - * Safely validates if a webview URI is from the trusted vscode-cdn.net host - * Prevents host injection attacks by properly parsing the URL - */ -function isValidVsCodeCdnHost(webviewUri: string): boolean { - try { - const url = new URL(webviewUri) - return url.host === "vscode-cdn.net" - } catch { - // URL parsing failed - not a valid URL - return false - } -} function getMimeTypeFromPath(filePath: string): string { const ext = path.extname(filePath).toLowerCase() diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index f35dce0912..05069bf6eb 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -652,6 +652,15 @@ export const ChatTextArea = forwardRef( // This ensures the frontend never retains image data URLs const pendingImageUploadsRef = useRef>(new Set()) + // Cleanup pending uploads on unmount to prevent leaks + useEffect(() => { + // Capture ref value to avoid stale closure + const uploads = pendingImageUploadsRef.current + return () => { + uploads.clear() + } + }, []) + const handlePaste = useCallback( async (e: React.ClipboardEvent) => { const items = e.clipboardData.items