From 0f2eaae06721fd0352c082ac6fd0dc460a136e1a Mon Sep 17 00:00:00 2001 From: Andrew Harvard Date: Thu, 5 Feb 2026 09:29:05 -0500 Subject: [PATCH] Apply CSP to inner content iframe to enforce restrictions --- .../goose-server/src/routes/mcp_app_proxy.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/crates/goose-server/src/routes/mcp_app_proxy.rs b/crates/goose-server/src/routes/mcp_app_proxy.rs index 10f58ea280..3aa953e8cd 100644 --- a/crates/goose-server/src/routes/mcp_app_proxy.rs +++ b/crates/goose-server/src/routes/mcp_app_proxy.rs @@ -334,11 +334,19 @@ async fn get_content(State(state): State, Path(token): Path) - return (StatusCode::NOT_FOUND, "Token not found or expired").into_response(); }; - // Serve the actual MCP App HTML - // Note: CSP is already enforced by the outer iframe, but we add minimal headers here too + // Build CSP for the content iframe - this is where the actual restrictions matter! + // The outer iframe's CSP doesn't cascade to iframes loading from different URLs. + let csp = build_csp(&app.csp); + let permission_policy = build_permission_policy(&app.permissions); + let mut response = Html(app.html).into_response(); let headers = response.headers_mut(); + // Apply CSP to restrict what the MCP App can load + headers.insert( + header::HeaderName::from_static("content-security-policy"), + csp.parse().unwrap(), + ); headers.insert( header::HeaderName::from_static("referrer-policy"), "no-referrer".parse().unwrap(), @@ -348,6 +356,13 @@ async fn get_content(State(state): State, Path(token): Path) - "nosniff".parse().unwrap(), ); + if let Some(pp) = permission_policy { + headers.insert( + header::HeaderName::from_static("permissions-policy"), + pp.parse().unwrap(), + ); + } + response }