From 2c0c2c467f163cd24c14c7be2db07af9cf2ce020 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Sat, 30 May 2026 12:05:19 +0200 Subject: [PATCH] fix(apps): make public apps opt into cross-origin isolation via wm_coep (GIT-884) (#9374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(apps): make public apps opt into cross-origin isolation via wm_coep Public app pages served at /public/* and custom paths /a/* were not getting the COEP/COOP/CORP headers, so they were blocked when embedded as an iframe inside a cross-origin-isolated page (e.g. another raw app, which sets Cross-Origin-Embedder-Policy: require-corp). A nested document loaded into a require-corp context must itself set COEP for the iframe to load. Rather than applying the isolation headers to all public pages (which would also force COEP on classic apps and break subresources without CORP, e.g. external image URLs or embeds), public apps now opt in via a `wm_coep` query param on the embed URL: ` + } async function getSecretUrl() { secretUrl = await AppService.getPublicSecretOfApp({ workspace: $workspaceStore!, @@ -253,12 +270,34 @@ {#if appPath == ''} {:else if secretUrlHref} - +
+ (embedMode = e.detail)} + options={{ left: 'URL', right: 'Embed' }} + /> +
+ {:else} {/if}
- Share this url directly or embed it using an iframe (if requiring login, top-level domain of - embedding app must be the same as the one of Windmill) + {#if embedMode} + Paste this iframe snippet into another app. + {#if rawApp} + The wm_coep flag Sets the cross-origin isolation headers (COEP) so the app can be embedded inside + another Windmill app or any cross-origin-isolated page. Without it the browser blocks + the iframe. lets it load inside a cross-origin-isolated page. + {/if} + (if requiring login, top-level domain of embedding app must be the same as the one of Windmill) + {:else} + Share this url directly, or switch to Embed to get an iframe snippet. + {/if}
@@ -305,7 +344,10 @@
Custom public URL
- +
{dirtyCustomPath ? customPathError : ''} diff --git a/frontend/src/lib/components/apps/editor/PublicApp.svelte b/frontend/src/lib/components/apps/editor/PublicApp.svelte index 48c3d5578d..fef6ba8fa3 100644 --- a/frontend/src/lib/components/apps/editor/PublicApp.svelte +++ b/frontend/src/lib/components/apps/editor/PublicApp.svelte @@ -145,7 +145,7 @@ name: $userStore?.name, groups: $userStore?.groups, username: $userStore?.username, - query: urlParamsToObject(page.url.searchParams), + query: urlParamsToObject(page.url.searchParams, { stripReserved: true }), hash: page.url.hash.substring(1) }} workspace={effectiveWorkspace} diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index 495a4ebcc1..35878e789b 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -837,6 +837,7 @@ {appPath} {onLatest} {savedApp} + rawApp bind:summary bind:customPath bind:deploymentMsg diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 8fb6475806..2e6302fb6c 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -1186,9 +1186,22 @@ export function isCodeInjection(expr: string | undefined): boolean { return dynamicTemplateRegex.test(expr) } -export function urlParamsToObject(params: URLSearchParams): Record { +// Query params Windmill consumes internally and that should not be exposed to +// app logic via the `query` context. Only params we actually own are listed +// here — the `wm_` prefix is a naming convention, not a reserved namespace, so +// we don't strip it wholesale (that would break apps reading their own `wm_*` +// params). `wm_coep` is a transport flag for cross-origin isolation headers. +export const WINDMILL_RESERVED_QUERY_PARAMS = new Set(['wm_coep']) + +export function urlParamsToObject( + params: URLSearchParams, + opts?: { stripReserved?: boolean } +): Record { const result: Record = {} params.forEach((value, key) => { + if (opts?.stripReserved && WINDMILL_RESERVED_QUERY_PARAMS.has(key)) { + return + } result[key] = value }) return result diff --git a/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte index bd900b38f0..737ebb2e33 100644 --- a/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte @@ -56,7 +56,7 @@ name: $userStore?.name, username: $userStore?.username, groups: $userStore?.groups, - query: urlParamsToObject(page.url.searchParams), + query: urlParamsToObject(page.url.searchParams, { stripReserved: true }), hash: page.url.hash.substring(1) }} workspace={$workspaceStore ?? ''}