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);
+ }
}