fix(security): block script execution in served SVGs via CSP

Serve uploaded SVGs (admin logos etc.) with a restrictive
Content-Security-Policy (default-src 'none'; style-src 'unsafe-inline';
img-src 'self' data:) + X-Content-Type-Options: nosniff in secureStatic.
The browser still renders the vector, but any embedded <script>/on*
handler can't execute if the SVG is opened directly — keeps real SVGs
(scalable) instead of rasterising them. Applies to all secureStatic
mounts (uploads/photos/thumbnails/fonts); only SVGs get the header.
This commit is contained in:
Luca
2026-06-03 14:37:40 +02:00
parent 02742b3163
commit 807ae3d4fa
+16 -1
View File
@@ -31,7 +31,22 @@ function secureStatic(basePath, options = {}) {
// Disable directory listing for security
index: false,
// Don't allow dotfiles
dotfiles: 'deny'
dotfiles: 'deny',
setHeaders: (resp, filePath) => {
// Preserve any caller-provided header logic (e.g. font caching).
if (typeof options.setHeaders === 'function') options.setHeaders(resp, filePath);
// SVGs are admin-uploadable (logos, favicon). Served from our
// own origin, a malicious SVG opened directly could run embedded
// <script>/on* handlers (stored XSS). A restrictive CSP lets the
// browser RENDER the vector but blocks all script execution —
// so we keep real SVGs (scalable) instead of rasterising them.
// `default-src 'none'` already implies script-src 'none';
// style-src + img-src(data:) keep normal SVG rendering working.
if (/\.svg$/i.test(filePath)) {
resp.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:");
resp.setHeader('X-Content-Type-Options', 'nosniff');
}
}
});
return staticMiddleware(req, res, next);