managarten/packages/shared-utils/src/security-headers.ts
Till JS 807c5da26e fix(mukke): add media-src to CSP for audio playback from MinIO
Add mediaSrc option to shared security headers and configure mukke
to allow audio loading from minio.mana.how (S3 presigned URLs).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 09:40:56 +01:00

70 lines
2.3 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 '@manacore/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');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
// Content Security Policy
const cspDirectives = [
"default-src 'self'",
`script-src 'self' 'unsafe-inline' https://stats.mana.how https://glitchtip.mana.how ${scriptSrc.join(' ')}`.trim(),
"style-src 'self' 'unsafe-inline'",
`img-src 'self' data: 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('; '));
}