diff --git a/backend/index.ts b/backend/index.ts index 562765ed..b52e6525 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -15,6 +15,7 @@ import { Elysia } from 'elysia'; import { corsMiddleware } from './middleware/cors'; import { errorHandlerMiddleware } from './middleware/error-handler'; import { loggerMiddleware } from './middleware/logger'; +import { securityMiddleware } from './middleware/security'; // Database initialization import { initializeDatabase, closeDatabase } from './database'; @@ -87,6 +88,7 @@ const HOST = SERVER_ENV.HOST; const app = new Elysia() // Apply middleware .use(corsMiddleware) + .use(securityMiddleware) .use(errorHandlerMiddleware) .use(loggerMiddleware) diff --git a/backend/middleware/security.ts b/backend/middleware/security.ts new file mode 100644 index 00000000..8676cf25 --- /dev/null +++ b/backend/middleware/security.ts @@ -0,0 +1,61 @@ +import type { Elysia } from 'elysia'; + +/** + * Security Headers Middleware + * + * Applies Content Security Policy (CSP) and other security headers to all responses. + * + * CSP Configuration Rationale: + * - default-src 'self': Restrict all resources to same-origin by default + * - script-src 'self' 'unsafe-inline' 'unsafe-eval': Monaco Editor requires eval for web workers and inline scripts + * - style-src 'self' 'unsafe-inline': Monaco and Tailwind use inline styles + * - img-src 'self' data: blob:: Support base64 images and blob URLs from browser preview + * - connect-src 'self' ws: wss:: Allow WebSocket connections for real-time chat and file operations + * - font-src 'self' data:: Support custom fonts and data URIs + * - frame-ancestors 'none': Prevent clickjacking by disallowing embedding in iframes + * - frame-src 'self': Allow iframes from same origin (browser preview) + * - worker-src 'self' blob:: Monaco Editor uses web workers from blob URLs + * + * Additional Security Headers: + * - X-Content-Type-Options: nosniff - Prevent MIME type sniffing + * - X-Frame-Options: DENY - Additional clickjacking protection (redundant with CSP frame-ancestors but defense-in-depth) + * - Referrer-Policy: strict-origin-when-cross-origin - Limit referrer information leakage + * - Permissions-Policy: Controls browser features (camera, microphone, geolocation, etc.) + */ +export function securityMiddleware(app: Elysia) { + return app.onAfterHandle(({ set }) => { + // Content Security Policy + set.headers['Content-Security-Policy'] = [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline' 'unsafe-eval'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: blob:", + "connect-src 'self' ws: wss:", + "font-src 'self' data:", + "frame-ancestors 'none'", + "frame-src 'self'", + "worker-src 'self' blob:" + ].join('; '); + + // Prevent MIME type sniffing + set.headers['X-Content-Type-Options'] = 'nosniff'; + + // Clickjacking protection (defense-in-depth with CSP frame-ancestors) + set.headers['X-Frame-Options'] = 'DENY'; + + // Referrer policy - limit information leakage + set.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'; + + // Permissions policy - restrict browser features + set.headers['Permissions-Policy'] = [ + 'camera=()', + 'microphone=()', + 'geolocation=()', + 'payment=()', + 'usb=()', + 'magnetometer=()', + 'gyroscope=()', + 'accelerometer=()' + ].join(', '); + }); +}