mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 22:19:40 +02:00
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>
83 lines
3.1 KiB
TypeScript
83 lines
3.1 KiB
TypeScript
/**
|
|
* Shared security headers for SvelteKit web apps.
|
|
*
|
|
* Sets standard security headers (CSP, X-Frame-Options, etc.)
|
|
* with Umami analytics and GlitchTip error tracking pre-configured.
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* import { setSecurityHeaders } from '@mana/shared-utils/security-headers';
|
|
*
|
|
* const response = await resolve(event, { transformPageChunk: ... });
|
|
* setSecurityHeaders(response, {
|
|
* connectSrc: [authUrl, backendUrl],
|
|
* });
|
|
* return response;
|
|
* ```
|
|
*/
|
|
|
|
interface SecurityHeadersOptions {
|
|
/** Additional connect-src origins (auth URL, backend URL, etc.) */
|
|
connectSrc?: string[];
|
|
/** Additional script-src origins */
|
|
scriptSrc?: string[];
|
|
/** Additional img-src origins */
|
|
imgSrc?: string[];
|
|
/** Additional font-src origins */
|
|
fontSrc?: string[];
|
|
/** Additional media-src origins (audio/video sources) */
|
|
mediaSrc?: string[];
|
|
/** Override frame-ancestors (default: 'none') */
|
|
frameAncestors?: string;
|
|
}
|
|
|
|
/**
|
|
* Set standard security headers on a Response object.
|
|
* Includes Umami (stats.mana.how) and GlitchTip (glitchtip.mana.how) by default.
|
|
*/
|
|
export function setSecurityHeaders(response: Response, options: SecurityHeadersOptions = {}): void {
|
|
const {
|
|
connectSrc = [],
|
|
scriptSrc = [],
|
|
imgSrc = [],
|
|
fontSrc = [],
|
|
mediaSrc = [],
|
|
frameAncestors = "'none'",
|
|
} = options;
|
|
|
|
// Standard security headers
|
|
response.headers.set('X-Frame-Options', 'DENY');
|
|
response.headers.set('X-Content-Type-Options', 'nosniff');
|
|
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
// Permissions-Policy: allow microphone for `self` so dreams/memoro voice
|
|
// capture (getUserMedia) works on mana.how. `microphone=()` would block
|
|
// the API entirely — Chrome reports `[Violation] Permissions policy
|
|
// violation: microphone is not allowed in this document` and the
|
|
// permission dialog never appears, even if the user has explicitly
|
|
// granted access in OS + browser settings. Camera stays disallowed
|
|
// since no module needs it.
|
|
response.headers.set('Permissions-Policy', 'camera=(), microphone=(self), geolocation=(self)');
|
|
|
|
// Content Security Policy
|
|
const cspDirectives = [
|
|
"default-src 'self'",
|
|
// 'wasm-unsafe-eval' is required by @mana/local-llm to instantiate
|
|
// browser inference WebGPU runtimes (both the old WebLLM/MLC path
|
|
// and the current transformers.js/ONNX path). It only permits
|
|
// WebAssembly compilation, NOT eval()/new Function() — much narrower
|
|
// than the legacy 'unsafe-eval' source. Supported by all evergreen
|
|
// browsers.
|
|
`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(),
|
|
`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'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
`frame-ancestors ${frameAncestors}`,
|
|
];
|
|
|
|
response.headers.set('Content-Security-Policy', cspDirectives.filter(Boolean).join('; '));
|
|
}
|