From 77d3004a2fd7743ba6f4953b980650ea86dea656 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Thu, 25 Dec 2025 14:53:33 +0200 Subject: [PATCH] fix: add CORS headers to static assets for iframe context sharing (#7454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add CORS headers to static assets for iframe context sharing Add Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and Cross-Origin-Resource-Policy headers to static assets served by the backend to match the vite preview config. This enables the TypeScript worker to work correctly when ui_builder is loaded in an iframe. Fixes #7453 Co-authored-by: windmill-internal-app[bot] * fix: add CORS headers only for /apps_raw paths and force reload on navigation - Only add Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy, and Cross-Origin-Resource-Policy headers for /apps_raw/ paths (not all static assets) - Add frontend navigation handler to force page reload when navigating from non-apps_raw paths to /apps_raw/add or /apps_raw/edit - This ensures the TypeScript worker works correctly while avoiding CORS issues for external iframe embeds in other parts of the app Co-authored-by: Ruben Fiszel 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix: also reload when navigating from /apps/get_raw/ to apps_raw editor When viewing a raw app at /apps/get_raw/, the cross-origin isolation headers are not present. Force a page reload when navigating from this path to /apps_raw/add or /apps_raw/edit to ensure the headers are fetched from the server. Co-authored-by: Ruben Fiszel * Update +layout.svelte --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: windmill-internal-app[bot] Co-authored-by: Claude Co-authored-by: Ruben Fiszel Co-authored-by: Ruben Fiszel --- backend/windmill-api/src/static_assets.rs | 31 +++++++++++++++---- .../src/routes/(root)/(logged)/+layout.svelte | 19 +++++++++++- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index 48bd94081c..f3095bbac7 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -19,7 +19,7 @@ use mime_guess::mime; #[cfg(feature = "static_frontend")] use rust_embed::RustEmbed; -// Content Security Policy configuration +// Content Security Policy configuration #[cfg(feature = "static_frontend")] lazy_static::lazy_static! { static ref CSP_POLICY: String = std::env::var("CSP_POLICY").unwrap_or_default(); @@ -38,15 +38,24 @@ pub struct StaticFile(Uri); impl IntoResponse for StaticFile { fn into_response(self) -> Response { - let path = self.0.path().trim_start_matches('/'); - serve_path(path) + let original_path = self.0.path(); + let path = original_path.trim_start_matches('/'); + serve_path(path, original_path) } } #[cfg(feature = "static_frontend")] const TWO_HUNDRED: &str = "200.html"; -fn serve_path(path: &str) -> Response { +/// Check if the original path requires cross-origin isolation headers +/// These headers are needed for SharedArrayBuffer and TypeScript workers +/// Only enabled for /apps_raw paths (raw app editor) +#[cfg(feature = "static_frontend")] +fn needs_cross_origin_isolation(original_path: &str) -> bool { + original_path.starts_with("/apps_raw/") +} + +fn serve_path(path: &str, original_path: &str) -> Response { if path.starts_with("api/") { return Response::builder().status(404).body(Body::empty()).unwrap(); } @@ -59,7 +68,16 @@ fn serve_path(path: &str) -> Response { let mut res = Response::builder() .header(header::CONTENT_TYPE, mime.as_ref()) .header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*"); - + + // Add cross-origin isolation headers only for paths that need them + // (apps_raw editor needs SharedArrayBuffer for TypeScript workers) + if needs_cross_origin_isolation(original_path) { + res = res + .header("Cross-Origin-Opener-Policy", "same-origin") + .header("Cross-Origin-Embedder-Policy", "require-corp") + .header("Cross-Origin-Resource-Policy", "cross-origin"); + } + // Add Content-Security-Policy header for static assets when policy is set if !CSP_POLICY.is_empty() { if let Ok(header_value) = HeaderValue::try_from(CSP_POLICY.as_str()) { @@ -84,11 +102,12 @@ fn serve_path(path: &str) -> Response { None if path.starts_with("_app/") => { Response::builder().status(404).body(Body::empty()).unwrap() } - None => serve_path(TWO_HUNDRED), + None => serve_path(TWO_HUNDRED, original_path), } #[cfg(not(feature = "static_frontend"))] { + let _ = original_path; // suppress unused warning Response::builder().status(404).body(Body::empty()).unwrap() } } diff --git a/frontend/src/routes/(root)/(logged)/+layout.svelte b/frontend/src/routes/(root)/(logged)/+layout.svelte index f15cd48b76..c50fa16bcf 100644 --- a/frontend/src/routes/(root)/(logged)/+layout.svelte +++ b/frontend/src/routes/(root)/(logged)/+layout.svelte @@ -113,8 +113,25 @@ } } - beforeNavigate(() => { + beforeNavigate((navigation) => { menuOpen = false + + // Force page reload when navigating to /apps_raw/add or /apps_raw/edit + // This ensures the cross-origin isolation headers are fetched from the server + // which are required for SharedArrayBuffer and TypeScript workers to work correctly + const toPath = navigation.to?.url.pathname + if ( + toPath && + (toPath.startsWith('/apps_raw/add') || toPath.startsWith('/apps_raw/edit')) + ) { + const currentPath = navigation.from?.url.pathname + // Reload if we're not on an apps_raw path, or if we're on /apps/get_raw/ (viewing a raw app) + // The /apps/get_raw/ path doesn't have cross-origin isolation headers, so we need to reload + if (!currentPath?.startsWith('/apps_raw/') || currentPath?.startsWith('/apps_raw/get/')) { + navigation.cancel() + window.location.href = navigation.to!.url.href + } + } }) let innerWidth = $state(BROWSER ? window.innerWidth : 2000)