tractatus/src/middleware/security-headers.middleware.js
TheFlow 3f2cd142ed feat: self-host all CDN assets — zero external dependencies
- Self-hosted: highlight.js (core + 5 language packs), marked.js, Chart.js
- CSP cleaned: removed cdn.jsdelivr.net, cdnjs.cloudflare.com,
  fonts.googleapis.com, fonts.gstatic.com
- Koha transparency page: Chart.js now self-hosted
- Tractatus now loads zero assets from external CDNs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 18:35:49 +13:00

71 lines
2.2 KiB
JavaScript

/**
* Security Headers Middleware (inst_044 - Quick Win Version)
* Implements comprehensive HTTP security headers
*
* QUICK WIN: Low effort, high value security improvement
* - Prevents XSS, clickjacking, MIME sniffing attacks
* - Enforces HTTPS, limits referrer leakage
* - Restricts dangerous browser features
*/
/**
* Apply security headers to all HTTP responses
*/
function securityHeadersMiddleware(req, res, next) {
// Content Security Policy (enforces inst_008 at HTTP level)
// Allows Tailwind inline styles, blocks inline scripts
res.setHeader(
'Content-Security-Policy',
[
"default-src 'self'",
"script-src 'self'", // All JS self-hosted
"style-src 'self' 'unsafe-inline'", // Tailwind (self-hosted fonts)
"img-src 'self' data: https:",
"font-src 'self'", // Self-hosted fonts only (no Google, no CDN)
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
'upgrade-insecure-requests'
].join('; ')
);
// Prevent MIME type sniffing attacks
res.setHeader('X-Content-Type-Options', 'nosniff');
// Prevent clickjacking via iframes
res.setHeader('X-Frame-Options', 'DENY');
// Enable browser XSS filter (legacy browsers)
res.setHeader('X-XSS-Protection', '1; mode=block');
// Enforce HTTPS (HSTS) - only add if HTTPS is available
if (req.secure || req.get('x-forwarded-proto') === 'https') {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains' // 1 year
);
}
// Limit referrer information leakage
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Restrict dangerous browser features
res.setHeader(
'Permissions-Policy',
'geolocation=(), microphone=(), camera=(), payment=()'
);
// Cache Control: NEVER cache admin files or API responses
if (req.path.startsWith('/admin/') ||
req.path.startsWith('/js/admin/') ||
req.path.startsWith('/api/')) {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
res.setHeader('Expires', '0');
}
next();
}
module.exports = { securityHeadersMiddleware };