fix(security): re-apply SVG CSP on the direct favicon route (PR #603 blocker)

The /favicon.ico + /apple-touch-icon routes stream the file directly,
bypassing the secureStatic middleware that locks down served SVGs. An
admin-uploaded SVG favicon with <script> would then run at the top-level
origin (stored XSS). Re-apply the same CSP (default-src 'none') + nosniff
for .svg here, mirroring secureStatic.js. Reported in the #603 review.
This commit is contained in:
Luca
2026-06-04 21:49:27 +02:00
parent fe56d24a6b
commit 1214b6b762
+9
View File
@@ -585,6 +585,15 @@ app.get(
const resolved = path.resolve(path.join(uploadsRoot, rel)); const resolved = path.resolve(path.join(uploadsRoot, rel));
// Path containment — never serve outside the uploads dir. // Path containment — never serve outside the uploads dir.
if (resolved.startsWith(uploadsRoot + path.sep) && fs.existsSync(resolved)) { if (resolved.startsWith(uploadsRoot + path.sep) && fs.existsSync(resolved)) {
// This route streams the file directly, bypassing the secureStatic
// middleware — so re-apply its SVG hardening here. An admin-uploaded
// SVG favicon could contain <script>; served at the top-level
// /favicon.ico origin without CSP that would be stored XSS. Keep in
// sync with secureStatic.js.
if (/\.svg$/i.test(resolved)) {
res.setHeader('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:");
res.setHeader('X-Content-Type-Options', 'nosniff');
}
res.setHeader('Cache-Control', 'public, max-age=86400'); res.setHeader('Cache-Control', 'public, max-age=86400');
return res.sendFile(resolved); return res.sendFile(resolved);
} }