From cebeec6cdb119f22e38b2da91e40bdff0f3f9b09 Mon Sep 17 00:00:00 2001
From: Frederic <1665799+Finomosec@users.noreply.github.com>
Date: Thu, 14 May 2026 10:54:26 +0200
Subject: [PATCH] ImageController: only force download for non-raster image
content types
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Address review feedback on the previous commit, which broadly removed
`Content-Disposition: attachment` and thereby re-opened
CVE-2024-43801 / GHSA-vcmh-9wx9-rfqh (an SVG profile picture rendered
inline by the browser can execute JavaScript in the Jellyfin origin
and exfiltrate the viewer's access token from localStorage).
Restore the security mitigation, but make it conditional: only set
`Content-Disposition: attachment` when the response is NOT a known
inline-safe raster format. Raster images (JPEG, PNG, WebP, GIF, AVIF,
BMP, TIFF, ICO) cannot execute scripts and are safe to load inline,
which is what enables browser caching in
contexts. SVG and any
other / unknown content type continue to be served as attachments,
preserving the original CVE fix.
The `Vary: Accept` removal from the previous commit is kept — it has
no security implication and is needed for the cache fix.
---
Jellyfin.Api/Controllers/ImageController.cs | 47 +++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index 2a31303feb..f5a7426983 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -2010,6 +2010,18 @@ public class ImageController : BaseJellyfinApiController
Response.ContentType = imageContentType ?? MediaTypeNames.Text.Plain;
Response.Headers.Append(HeaderNames.Age, Convert.ToInt64((DateTime.UtcNow - dateImageModified).TotalSeconds).ToString(CultureInfo.InvariantCulture));
+ // CVE-2024-43801 / GHSA-vcmh-9wx9-rfqh: an SVG profile picture rendered
+ // inline by the browser can execute JavaScript in the Jellyfin origin and
+ // exfiltrate the viewer's access token from localStorage. Keep forcing
+ // anything that is not a known-safe raster image to be downloaded rather
+ // than displayed inline. Raster formats cannot execute scripts, so we
+ // allow them to load inline — this also lets browsers cache them
+ // effectively for
consumers.
+ if (!IsInlineSafeImageContentType(imageContentType))
+ {
+ Response.Headers.ContentDisposition = "attachment";
+ }
+
if (disableCaching)
{
Response.Headers.Append(HeaderNames.CacheControl, "no-cache, no-store, must-revalidate");
@@ -2078,4 +2090,39 @@ public class ImageController : BaseJellyfinApiController
return false;
}
+
+ // Allow-list of raster image content types that cannot execute scripts and
+ // are therefore safe to load inline in an
tag. Anything outside this
+ // list (most notably image/svg+xml — see CVE-2024-43801) must be served
+ // with Content-Disposition: attachment so the browser downloads rather
+ // than renders it.
+ private static readonly HashSet _inlineSafeImageContentTypes
+ = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "image/jpeg",
+ "image/png",
+ "image/webp",
+ "image/gif",
+ "image/avif",
+ "image/bmp",
+ "image/tiff",
+ "image/x-icon",
+ "image/vnd.microsoft.icon"
+ };
+
+ private static bool IsInlineSafeImageContentType(string? contentType)
+ {
+ if (string.IsNullOrEmpty(contentType))
+ {
+ return false;
+ }
+
+ if (!MediaTypeHeaderValue.TryParse(contentType, out var parsed)
+ || !parsed.MediaType.HasValue)
+ {
+ return false;
+ }
+
+ return _inlineSafeImageContentTypes.Contains(parsed.MediaType.Value);
+ }
}