Procházet zdrojové kódy

Enhance CSP handling for Remote-SSH compatibility

paviko před 6 dny
rodič
revize
c7c4fb13e5

+ 1 - 1
hosts/vscode-plugin/resources/webview/index.html

@@ -5,7 +5,7 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <meta
       http-equiv="Content-Security-Policy"
-      content="default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' http://127.0.0.1:* https://127.0.0.1:* ${cspSource}; style-src 'unsafe-inline' http://127.0.0.1:* https://127.0.0.1:* ${cspSource}; img-src 'self' data: http://127.0.0.1:* https://127.0.0.1:* https://*.vscode-cdn.net; connect-src ws://127.0.0.1:* wss://127.0.0.1:* http://127.0.0.1:* https://127.0.0.1:*; font-src 'self' data: http://127.0.0.1:* https://127.0.0.1:*; media-src 'self' http://127.0.0.1:* https://127.0.0.1:*; frame-src http://127.0.0.1:* https://127.0.0.1:*; object-src 'none'; base-uri 'none'"
+      content="default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' ${cspOrigins} ${cspSource}; style-src 'unsafe-inline' ${cspOrigins} ${cspSource}; img-src 'self' data: ${cspOrigins} https://*.vscode-cdn.net; connect-src ws://127.0.0.1:* wss://127.0.0.1:* ws://localhost:* wss://localhost:* ${cspOrigins}; font-src 'self' data: ${cspOrigins}; media-src 'self' ${cspOrigins}; frame-src ${cspOrigins}; object-src 'none'; base-uri 'none'"
     />
     <title>OpenCode</title>
     <style>

+ 43 - 3
hosts/vscode-plugin/src/ui/WebviewController.ts

@@ -111,7 +111,11 @@ export class WebviewController {
       const uiUrlWithMode = this.buildUiUrlWithMode(externalUi.toString())
       const iframeSrc = `${uiUrlWithMode}&ideBridge=${encodeURIComponent(externalBridge.toString())}&ideBridgeToken=${encodeURIComponent(session.token)}`
 
-      const html = await this.generateHtmlContent(iframeSrc)
+      // Extract origins for dynamic CSP (Remote-SSH compatibility)
+      const uiOrigin = new URL(externalUi.toString()).origin
+      const bridgeOrigin = new URL(externalBridge.toString()).origin
+
+      const html = await this.generateHtmlContent(iframeSrc, { uiOrigin, bridgeOrigin })
       this.webview.html = html
 
       // Message handling is now done entirely by CommunicationBridge
@@ -226,14 +230,50 @@ export class WebviewController {
     return base.includes("?") ? `${base}&mode=${uiMode}` : `${base}?mode=${uiMode}`
   }
 
-  private async generateHtmlContent(uiUrl: string): Promise<string> {
+  private async generateHtmlContent(
+    uiUrl: string,
+    origins: { uiOrigin: string; bridgeOrigin: string },
+  ): Promise<string> {
     const htmlUri = vscode.Uri.joinPath(this.context.extensionUri, "resources", "webview", "index.html")
     const bytes = await vscode.workspace.fs.readFile(htmlUri)
     let html = Buffer.from(bytes).toString("utf8")
-    html = html.replace(/\$\{uiUrl\}/g, uiUrl).replace(/\$\{cspSource\}/g, this.webview.cspSource)
+
+    // Build dynamic CSP origins - include both specific origins and localhost fallbacks
+    const cspOrigins = this.buildCspOrigins(origins.uiOrigin, origins.bridgeOrigin)
+
+    html = html
+      .replace(/\$\{uiUrl\}/g, uiUrl)
+      .replace(/\$\{cspSource\}/g, this.webview.cspSource)
+      .replace(/\$\{cspOrigins\}/g, cspOrigins)
+
     return html
   }
 
+  private buildCspOrigins(uiOrigin: string, bridgeOrigin: string): string {
+    // Collect unique origins, always include localhost fallbacks for compatibility
+    const origins = new Set<string>([
+      "http://127.0.0.1:*",
+      "https://127.0.0.1:*",
+      "http://localhost:*",
+      "https://localhost:*",
+    ])
+
+    // Add the actual resolved origins (handles Remote-SSH tunnels, codespaces, etc.)
+    for (const origin of [uiOrigin, bridgeOrigin]) {
+      try {
+        const url = new URL(origin)
+        // Add with wildcard port for flexibility
+        origins.add(`${url.protocol}//${url.hostname}:*`)
+        // Also add the exact origin
+        origins.add(origin)
+      } catch {
+        // Skip invalid origins
+      }
+    }
+
+    return Array.from(origins).join(" ")
+  }
+
   private normalizePath(rawPath: string): string | null {
     try {
       if (!rawPath || rawPath.trim().length === 0) return null