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