fix(csp): move jsdelivr allowlist to mana-web hooks (Vite SSR cache workaround)

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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-08 23:03:38 +02:00
parent 96023394b5
commit e4e3360ca8
2 changed files with 12 additions and 13 deletions

View file

@ -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:

View file

@ -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'",