ImageController: only force download for non-raster image content types

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 <img> 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.
This commit is contained in:
Frederic 2026-05-14 10:54:26 +02:00
parent 43b28ec55f
commit cebeec6cdb

View file

@ -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 <img> 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 <img> 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<string> _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);
}
}