diff --git a/crates/goose-server/src/routes/mcp_app_proxy.rs b/crates/goose-server/src/routes/mcp_app_proxy.rs index bf261127ba70..7ff045c20555 100644 --- a/crates/goose-server/src/routes/mcp_app_proxy.rs +++ b/crates/goose-server/src/routes/mcp_app_proxy.rs @@ -14,6 +14,10 @@ struct ProxyQuery { connect_domains: Option, /// Comma-separated list of domains for resource loading (scripts, styles, images, fonts, media) resource_domains: Option, + /// Comma-separated list of origins for nested iframes (frame-src) + frame_domains: Option, + /// Comma-separated list of allowed base URIs (base-uri) + base_uri_domains: Option, } const MCP_APP_PROXY_HTML: &str = include_str!("templates/mcp_app_proxy.html"); @@ -26,7 +30,12 @@ const MCP_APP_PROXY_HTML: &str = include_str!("templates/mcp_app_proxy.html"); /// /// Based on the MCP Apps specification (ext-apps SEP): /// -fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> String { +fn build_outer_csp( + connect_domains: &[String], + resource_domains: &[String], + frame_domains: &[String], + base_uri_domains: &[String], +) -> String { let resources = if resource_domains.is_empty() { String::new() } else { @@ -39,6 +48,18 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S format!(" {}", connect_domains.join(" ")) }; + let frame_src = if frame_domains.is_empty() { + "frame-src 'none'".to_string() + } else { + format!("frame-src {}", frame_domains.join(" ")) + }; + + let base_uris = if base_uri_domains.is_empty() { + String::new() + } else { + format!(" {}", base_uri_domains.join(" ")) + }; + format!( "default-src 'none'; \ script-src 'self' 'unsafe-inline'{resources}; \ @@ -49,9 +70,9 @@ fn build_outer_csp(connect_domains: &[String], resource_domains: &[String]) -> S img-src 'self' data: blob:{resources}; \ font-src 'self'{resources}; \ media-src 'self' data: blob:{resources}; \ - frame-src blob: data:; \ + {frame_src}; \ object-src 'none'; \ - base-uri 'self'" + base-uri 'self'{base_uris}" ) } @@ -73,7 +94,9 @@ fn parse_domains(domains: Option<&String>) -> Vec { params( ("secret" = String, Query, description = "Secret key for authentication"), ("connect_domains" = Option, Query, description = "Comma-separated domains for connect-src"), - ("resource_domains" = Option, Query, description = "Comma-separated domains for resource loading") + ("resource_domains" = Option, Query, description = "Comma-separated domains for resource loading"), + ("frame_domains" = Option, Query, description = "Comma-separated origins for nested iframes (frame-src)"), + ("base_uri_domains" = Option, Query, description = "Comma-separated allowed base URIs (base-uri)") ), responses( (status = 200, description = "MCP App proxy HTML page", content_type = "text/html"), @@ -91,9 +114,16 @@ async fn mcp_app_proxy( // Parse domains from query params let connect_domains = parse_domains(params.connect_domains.as_ref()); let resource_domains = parse_domains(params.resource_domains.as_ref()); + let frame_domains = parse_domains(params.frame_domains.as_ref()); + let base_uri_domains = parse_domains(params.base_uri_domains.as_ref()); // Build the outer CSP based on declared domains - let csp = build_outer_csp(&connect_domains, &resource_domains); + let csp = build_outer_csp( + &connect_domains, + &resource_domains, + &frame_domains, + &base_uri_domains, + ); // Replace the CSP placeholder in the HTML template let html = MCP_APP_PROXY_HTML.replace("{{OUTER_CSP}}", &csp); diff --git a/ui/desktop/src/components/McpApps/utils.ts b/ui/desktop/src/components/McpApps/utils.ts index a999123f1cfa..d0dada06274c 100644 --- a/ui/desktop/src/components/McpApps/utils.ts +++ b/ui/desktop/src/components/McpApps/utils.ts @@ -9,6 +9,8 @@ export async function fetchMcpAppProxyUrl( csp?: { connectDomains?: string[] | null; resourceDomains?: string[] | null; + frameDomains?: string[] | null; + baseUriDomains?: string[] | null; } | null ): Promise { try { @@ -28,6 +30,12 @@ export async function fetchMcpAppProxyUrl( if (csp?.resourceDomains?.length) { params.set('resource_domains', csp.resourceDomains.join(',')); } + if (csp?.frameDomains?.length) { + params.set('frame_domains', csp.frameDomains.join(',')); + } + if (csp?.baseUriDomains?.length) { + params.set('base_uri_domains', csp.baseUriDomains.join(',')); + } return `${baseUrl}/mcp-app-proxy?${params.toString()}`; } catch (error) {