/** * Security headers middleware * Adds security-related HTTP headers to all responses. */ export function securityHeaders(req, res, next) { // Prevent MIME type sniffing res.setHeader('X-Content-Type-Options', 'nosniff'); // Do not set X-Frame-Options to DENY because Hugging Face Spaces // renders the app inside an iframe. // XSS protection res.setHeader('X-XSS-Protection', '1; mode=block'); // Referrer policy res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); // Content Security Policy (basic) res.setHeader( 'Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self' https://huggingface.co https://*.hf.space;" ); // Surface request latency via Server-Timing to aid production diagnostics. // Wrap res.end so the elapsed time is known before headers are flushed. const startMs = Date.now(); const originalEnd = res.end.bind(res); res.end = function (chunk, encoding, callback) { if (!res.headersSent) { res.setHeader('Server-Timing', `total;dur=${Date.now() - startMs}`); } return originalEnd(chunk, encoding, callback); }; // Remove server identification res.removeHeader('X-Powered-By'); next(); } /** * Global error handler that sanitizes error output in production */ export function errorHandler(err, _req, res, _next) { const statusCode = err.statusCode || err.status || 500; // Never expose stack traces in production const isProduction = process.env.NODE_ENV === 'production'; if (statusCode >= 500) { console.error('Server error:', err.message, isProduction ? '' : err.stack); } res.status(statusCode).json({ success: false, message: isProduction ? 'An internal error occurred' : err.message, ...(isProduction ? {} : { stack: err.stack }), }); } export default { securityHeaders, errorHandler };