- 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>
71 lines
2.2 KiB
JavaScript
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 };
|