From d981b4e6b39ed45d285187ff474806d62fcf0384 Mon Sep 17 00:00:00 2001 From: miljanm <1547789+miljanm@users.noreply.github.com> Date: Tue, 21 Jul 2026 23:26:41 +0000 Subject: [PATCH] app-frame: fall back to same-origin module import when CSP blocks blob: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A deployment whose edge proxy serves a script-src without blob: (an app-code vs edge-CSP version skew) blocked every app with an opaque 'Failed to fetch dynamically imported module: blob:null/...' error. The loader now falls back to a direct same-origin /module import (the mechanism used before brokered-blob loading), which script-src 'self' always permits, so app loading self-heals across that skew instead of a total outage. Co-authored-by: Möbius Agent --- frontend/public/app-frame.html | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/frontend/public/app-frame.html b/frontend/public/app-frame.html index 07619f72..338341af 100644 --- a/frontend/public/app-frame.html +++ b/frontend/public/app-frame.html @@ -617,12 +617,40 @@ }); } + // Fallback loader for environments whose CSP forbids executing a blob: + // module. The blob path (below) needs `script-src ... blob:`; a deployment + // whose edge proxy serves an older policy WITHOUT blob: (an app-code vs. + // edge-CSP version skew) blocks EVERY app with an opaque + // "Failed to fetch dynamically imported module: blob:null/..." error. This + // imports the compiled module directly from its same-origin /module URL, + // which `script-src 'self'` always permits (the mechanism the frame used + // before brokered-blob loading). The token rides as a query param because + // dynamic import() can't set headers; a cache-buster avoids Chromium + // resolving a previously-cached import failure. Self-contained bundle, so no + // relative/bare sub-imports depend on the URL. This keeps a stale-edge + // deployment fully working instead of dark, and self-heals once the edge + // policy is corrected (the blob path simply stops throwing). + async function importDirectModule() { + const url = '/api/apps/' + encodeURIComponent(_FRAME_APP_ID) + + '/module?token=' + encodeURIComponent(currentToken) + + '&_=' + Date.now(); + return await import(url); + } + async function importBrokeredModule(retry) { const bytes = await requestModuleBytes(retry); const blob = new Blob([bytes], { type: 'text/javascript' }); const blobUrl = URL.createObjectURL(blob); try { return await import(blobUrl); + } catch (blobImportErr) { + // The blob module itself failed to execute. The dominant cause in + // practice is a CSP that forbids blob: scripts (surfaced as a TypeError + // "Failed to fetch dynamically imported module: blob:null/..."); a + // genuine syntax/eval error in the bundle would fail identically on the + // direct path and fall through to the error surface, so the fallback is + // safe either way. + return await importDirectModule(); } finally { URL.revokeObjectURL(blobUrl); }