From e4e3360ca85b36582eecbd1b5da7e52b31fa1434 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 8 Apr 2026 23:03:38 +0200 Subject: [PATCH] fix(csp): move jsdelivr allowlist to mana-web hooks (Vite SSR cache workaround) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous two attempts at allowlisting cdn.jsdelivr.net for transformers.js's onnxruntime-web loader landed in shared-utils security-headers.ts. The actual file change was correct (verified by grep), the commits got pushed, the live security-headers.ts on disk had the additions — but Vite's SSR module cache for cross-workspace- package imports kept serving the OLD compiled shared-utils to hooks.server.ts. Net effect: edits to hooks.server.ts hot-reloaded fine (proven by the *.hf.co connect-src additions showing up immediately) while edits to shared-utils/security-headers.ts did not. A dev server restart should clear it but I'd rather not depend on manual intervention every time we touch the shared CSP. Move the jsdelivr allowlist out of the shared default and into mana-web's hooks.server.ts via the existing scriptSrc + connectSrc options. hooks.server.ts is in the SvelteKit app's own source tree so it HMRs reliably, no SSR cache to fight. As a bonus this is also architecturally cleaner: cdn.jsdelivr.net is only needed by mana-web because mana-web is the only Mana app that bundles @mana/local-llm — other apps get a slightly tighter CSP for free. The pattern to remember: changes to packages/shared-utils that affect SSR (response headers, server hooks) require either a dev server restart OR a manual `rm -rf apps/.../node_modules/.vite` to take effect. Client-side changes hot-reload fine. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/mana/apps/web/src/hooks.server.ts | 10 ++++++++++ packages/shared-utils/src/security-headers.ts | 15 ++------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/apps/mana/apps/web/src/hooks.server.ts b/apps/mana/apps/web/src/hooks.server.ts index a5411edae..c15809951 100644 --- a/apps/mana/apps/web/src/hooks.server.ts +++ b/apps/mana/apps/web/src/hooks.server.ts @@ -103,6 +103,11 @@ window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)}; const isDev = process.env.NODE_ENV !== 'production'; setSecurityHeaders(response, { + // @huggingface/transformers (used by @mana/local-llm) lazy-loads the + // onnxruntime-web WASM loader from jsDelivr at backend selection + // time via a dynamic import(). Browsers route dynamic imports + // through script-src. + scriptSrc: ['https://cdn.jsdelivr.net'], connectSrc: [ PUBLIC_MANA_AUTH_URL_CLIENT, PUBLIC_SYNC_SERVER_URL_CLIENT, @@ -112,6 +117,11 @@ window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)}; PUBLIC_MANA_EVENTS_URL_CLIENT, PUBLIC_MANA_API_URL_CLIENT, 'wss://sync.mana.how', + // transformers.js *also* fetch()es the .wasm binary and the .mjs + // loader factory directly to pre-warm the runtime — those go + // through connect-src, not script-src, so jsDelivr has to be in + // both lists for the WebGPU backend resolver to succeed. + 'https://cdn.jsdelivr.net', // @mana/local-llm (transformers.js) pulls model config + ONNX // shards from the HuggingFace ecosystem. HF currently uses three // distinct CDN domains depending on file type and rollout state: diff --git a/packages/shared-utils/src/security-headers.ts b/packages/shared-utils/src/security-headers.ts index b62af52cd..5b2f35e93 100644 --- a/packages/shared-utils/src/security-headers.ts +++ b/packages/shared-utils/src/security-headers.ts @@ -67,21 +67,10 @@ export function setSecurityHeaders(response: Response, options: SecurityHeadersO // WebAssembly compilation, NOT eval()/new Function() — much narrower // than the legacy 'unsafe-eval' source. Supported by all evergreen // browsers. - // - // cdn.jsdelivr.net is allowlisted because @huggingface/transformers - // loads onnxruntime-web via a runtime dynamic `import()` from - // jsDelivr (the package itself is bundled, but the WASM-loader - // shim is fetched lazily so transformers.js v4 can pick the - // right backend without bloating the static bundle). - `script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://stats.mana.how https://glitchtip.mana.how https://cdn.jsdelivr.net ${scriptSrc.join(' ')}`.trim(), + `script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://stats.mana.how https://glitchtip.mana.how ${scriptSrc.join(' ')}`.trim(), "style-src 'self' 'unsafe-inline'", `img-src 'self' data: blob: https: ${imgSrc.join(' ')}`.trim(), - // jsDelivr also has to be in connect-src because @huggingface/transformers - // pre-loads the WASM binary and the loader .mjs via plain fetch() (not - // just dynamic import) when selecting the ONNX backend. The script-src - // allowlist alone covers the import() but not the fetch() — both are - // required for the WebGPU backend resolver to succeed. - `connect-src 'self' https://stats.mana.how https://glitchtip.mana.how https://cdn.jsdelivr.net ${connectSrc.join(' ')}`.trim(), + `connect-src 'self' https://stats.mana.how https://glitchtip.mana.how ${connectSrc.join(' ')}`.trim(), `font-src 'self' ${fontSrc.join(' ')}`.trim(), mediaSrc.length > 0 ? `media-src 'self' ${mediaSrc.join(' ')}`.trim() : '', "object-src 'none'",