'use strict'; const crypto = require('crypto'); /** * Generates a secure nonce for CSP policy. * Call once per request to get a unique nonce. */ function getCspNonce(req) { if (!req.cspNonce) { req.cspNonce = crypto.randomBytes(16).toString('base64'); } return req.cspNonce; } /** * Applies baseline security response headers on every request. * * Content Security Policy (CSP) is now implemented with nonce-based policies * to support Tailwind/shadcn inline styles and Vite build hashes. */ function securityHeaders(req, res, next) { // CSP Header - nonce-based policy for Tailwind and Vite const nonce = getCspNonce(req); const cspPolicy = `default-src 'self'; ` + `script-src 'self' 'nonce-${nonce}'; ` + `style-src 'self' 'unsafe-inline' 'nonce-${nonce}'; ` + `img-src 'self' data:; ` + `font-src 'self'; ` + `connect-src 'self'; ` + `frame-ancestors 'self'; ` + `form-action 'self'; ` + `base-uri 'self'; ` + `object-src 'none';`; res.setHeader('Content-Security-Policy', cspPolicy); // Prevent MIME-type sniffing (browsers must respect Content-Type) res.setHeader('X-Content-Type-Options', 'nosniff'); // Disallow embedding in iframes from other origins — prevents clickjacking res.setHeader('X-Frame-Options', 'SAMEORIGIN'); // Don't send full URL in Referer header to third-party origins res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); // Disallow cross-domain policy files (Flash/PDF legacy attack surface) res.setHeader('X-Permitted-Cross-Domain-Policies', 'none'); // Don't advertise the server tech stack res.removeHeader('X-Powered-By'); // HSTS — only set when the app is explicitly configured to run over HTTPS. // Setting this on HTTP breaks the site. Enable with HTTPS=true env var. if (process.env.HTTPS === 'true') { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); } next(); } module.exports = { securityHeaders, getCspNonce };