'use strict'; import type { Req, Res, Next } from '../types/http'; const crypto = require('crypto'); /** Generates a secure nonce for CSP policy. Call once per request. */ function getCspNonce(req: Req): string { if (!req.cspNonce) { req.cspNonce = crypto.randomBytes(16).toString('base64'); } return req.cspNonce; } /** * Applies baseline security response headers on every request. * (See original notes on why CSP uses no nonce — index.html is static.) */ function securityHeaders(req: Req, res: Res, next: Next): void { const cspPolicy = `default-src 'self'; ` + `script-src 'self'; ` + `style-src 'self' 'unsafe-inline'; ` + `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); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'SAMEORIGIN'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); res.setHeader('X-Permitted-Cross-Domain-Policies', 'none'); res.removeHeader('X-Powered-By'); // HSTS — only when explicitly configured to run over HTTPS. if (process.env.HTTPS === 'true') { res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); } next(); } module.exports = { securityHeaders, getCspNonce };