BillTracker/middleware/securityHeaders.js

64 lines
2.0 KiB
JavaScript
Raw Permalink Normal View History

2026-05-03 19:51:57 -05:00
'use strict';
2026-05-09 13:03:36 -05:00
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;
}
2026-05-03 19:51:57 -05:00
/**
* Applies baseline security response headers on every request.
2026-05-09 13:03:36 -05:00
*
* Content Security Policy (CSP) is now implemented with nonce-based policies
* to support Tailwind/shadcn inline styles and Vite build hashes.
2026-05-03 19:51:57 -05:00
*/
function securityHeaders(req, res, next) {
2026-05-09 13:03:36 -05:00
// 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);
2026-05-03 19:51:57 -05:00
// 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();
}
2026-05-09 13:03:36 -05:00
module.exports = { securityHeaders, getCspNonce };