64 lines
2.0 KiB
JavaScript
64 lines
2.0 KiB
JavaScript
'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 };
|