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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user