/** * Security Headers Middleware * Agent 4: Production Deployment * * Adds security headers to all responses to protect against common attacks. */ import type { NextApiRequest, NextApiResponse, NextApiHandler } from 'next'; import { env } from '../config'; interface SecurityHeadersConfig { contentSecurityPolicy?: string; reportUri?: string; enableHSTS?: boolean; hstsMaxAge?: number; frameOptions?: 'DENY' | 'SAMEORIGIN'; contentTypeOptions?: boolean; xssProtection?: boolean; referrerPolicy?: string; permissionsPolicy?: string; } const DEFAULT_CONFIG: SecurityHeadersConfig = { enableHSTS: true, hstsMaxAge: 31536000, // 1 year frameOptions: 'DENY', contentTypeOptions: true, xssProtection: true, referrerPolicy: 'strict-origin-when-cross-origin', }; /** * Generate Content Security Policy header */ function generateCSP(reportUri?: string): string { const directives = [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // Needed for Next.js "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob: https:", "font-src 'self' data:", "connect-src 'self' https: wss:", "media-src 'self'", "object-src 'none'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", "upgrade-insecure-requests", ]; if (reportUri) { directives.push(`report-uri ${reportUri}`); } return directives.join('; '); } /** * Generate Permissions Policy header */ function generatePermissionsPolicy(): string { const policies = [ 'accelerometer=()', 'camera=()', 'geolocation=(self)', 'gyroscope=()', 'magnetometer=()', 'microphone=()', 'payment=()', 'usb=()', ]; return policies.join(', '); } /** * Apply security headers to response */ export function applySecurityHeaders( res: NextApiResponse, config: SecurityHeadersConfig = DEFAULT_CONFIG ): void { // Content Security Policy const csp = config.contentSecurityPolicy || generateCSP(config.reportUri || env.cspReportUri); res.setHeader('Content-Security-Policy', csp); // Strict Transport Security (HTTPS) if (config.enableHSTS && env.isProduction) { res.setHeader( 'Strict-Transport-Security', `max-age=${config.hstsMaxAge}; includeSubDomains; preload` ); } // Prevent clickjacking res.setHeader('X-Frame-Options', config.frameOptions || 'DENY'); // Prevent MIME type sniffing if (config.contentTypeOptions) { res.setHeader('X-Content-Type-Options', 'nosniff'); } // XSS Protection (legacy, but still useful) if (config.xssProtection) { res.setHeader('X-XSS-Protection', '1; mode=block'); } // Referrer Policy res.setHeader('Referrer-Policy', config.referrerPolicy || 'strict-origin-when-cross-origin'); // Permissions Policy res.setHeader( 'Permissions-Policy', config.permissionsPolicy || generatePermissionsPolicy() ); // Remove unnecessary headers res.removeHeader('X-Powered-By'); } /** * Security headers middleware for API routes */ export function withSecurityHeaders( handler: NextApiHandler, config?: SecurityHeadersConfig ): NextApiHandler { return async (req: NextApiRequest, res: NextApiResponse) => { applySecurityHeaders(res, config); return handler(req, res); }; } // Export types export type { SecurityHeadersConfig };