[Issue]: Images resolved from the "Edit Images" modal aren't using the provider #6038

Open
opened 2025-12-22 03:27:19 +01:00 by backuprepo · 1 comment
Owner

Originally created by @revam on GitHub (Jun 20, 2024).

Please describe your bug

Follow up for #12074.

Currently the resolution of remote images provided by plugins are handled differently in two places;

  • If a remote image is acquired internally in Jellyfin it will ask the provider to fetch the image for us. As seen here and here.

  • If a remote image is asked by a client to be added to an item, it will always be downloaded using a standard HTTP client. As seen here. I'll also note that the client is sending the image url, provider name, and image type with each request. As seen here.

@thornbill pointed out that https://github.com/jellyfin/jellyfin/security/advisories/GHSA-rgjw-4fwc-9v96 existed in the matrix dev. channel, so the new solution must be resistant to this attack vector.

@cvium mentioned in https://github.com/jellyfin/jellyfin/pull/12074#issuecomment-2174448293

[…] Ideally, and this is just my opinion:

  1. All images are proxied through the Jellyfin backend

  2. Identify (and other remote download flows?) stores an ID per image

  3. Remote image requests always use the ID and based on the ID, the server knows what to download and store

EDIT: This is a pretty big API breaking change, but ultimately the only reasonable (imo) fix for the security advisory. To ease clients into it, we could create a new set of endpoints.

This issue exists to find the best way to solve it if cvium's solution is not enough, or for somebody qualified to undertake the "pretty big API breakage change" to have a reference and/or reminder that it needs fixing at some point.

Reproduction Steps

Context: Install an image provider that uses local images fetched from another local server. Make sure Jellyfin is using one route to the server that it's clients cannot resolve, and versa visa. Images returned from the provider is directly resolvable by Jellyfin's clients, but not by Jellyfin itself as a result, but the provider is able to resolve the image for Jellyfin.

What works:

  1. Open any entity in jellyfin.

  2. Remove any existing images on the entity.

  3. Click "Refresh metadata", then "Search for missing metadata" or "Replace all metadata", and make sure to enable "Replace existing images", to force it to download images.

  4. Let it run.

  5. No errors in the UI, and the log is not filled with errors

  6. The entity have images again.

What doesn't work:

  1. Open any entity in Jellyfin

  2. Remove any existing images on the entity.

  3. Open "Edit Images", followed by searching for new images. Observe that the images are properly displaying in the "Search" modal.

  4. Press "Download" on any of the images.

  5. It errs out on the server side and the UI doesn't properly get rid of the loading animation.

  6. Refresh to see that you still don't have images in the UI (since it erred out).

Jellyfin Version

10.9.0

if other:

No response

Environment

- OS: All
- Linux Kernel: none
- Virtualization: none
- Clients: All
- Browser: none
- FFmpeg Version: N/A
- Playback Method: none
- Hardware Acceleration: none
- GPU Model: none
- Plugins: Any
- Reverse Proxy: none
- Base URL: none
- Networking: Any
- Storage: Any

Jellyfin logs

[2024-06-10 17:24:17.925 -04:00] [ERR] [32] Jellyfin.Api.Middleware.ExceptionMiddleware: Error processing request. URL "POST" "/Items/afddb0774e997c04cbee139da8304d84/RemoteImages/Download".
System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
   at MediaBrowser.Providers.Manager.ProviderManager.SaveImage(BaseItem item, String url, ImageType type, Nullable`1 imageIndex, CancellationToken cancellationToken)
   at Jellyfin.Api.Controllers.RemoteImageController.DownloadRemoteImage(Guid itemId, ImageType type, String imageUrl)
   at lambda_method1269(Closure, Object)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Jellyfin.Api.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager)
   at Jellyfin.Api.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager)
   at Jellyfin.Api.Middleware.IPBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager)
   at Jellyfin.Api.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Api.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Jellyfin.Api.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext)
   at Jellyfin.Api.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context)
   at Jellyfin.Api.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager)
   at Jellyfin.Api.Middleware.ExceptionMiddleware.Invoke(HttpContext context)

FFmpeg logs

No response

Please attach any browser or client logs here

No response

Please attach any screenshots here

No response

Code of Conduct

  • I agree to follow this project's Code of Conduct
Originally created by @revam on GitHub (Jun 20, 2024). ### Please describe your bug Follow up for #12074. Currently the resolution of remote images provided by plugins are handled differently in two places; - If a remote image is acquired internally in Jellyfin it will ask the provider to fetch the image for us. As seen [here](https://github.com/jellyfin/jellyfin/blob/18dd6b2875b05d6b9c8249476ae509bfd20e8314/MediaBrowser.Providers/Manager/ItemImageProvider.cs#L535) and [here](https://github.com/jellyfin/jellyfin/blob/18dd6b2875b05d6b9c8249476ae509bfd20e8314/MediaBrowser.Providers/Manager/ItemImageProvider.cs#L644). - If a remote image is asked by a client to be added to an item, it will always be downloaded using a standard HTTP client. As seen [here](https://github.com/jellyfin/jellyfin/blob/6fb6b5f1766a1f37a61b9faaa40209bab995bf30/Jellyfin.Api/Controllers/RemoteImageController.cs#L166). I'll also note that the client is sending the image url, provider name, and image type with each request. As seen [here](https://github.com/jellyfin/jellyfin-web/blob/20381bd3ec2f23907ed53d7da568cae921b05506/src/components/imageDownloader/imageDownloader.js#L140-L154). @thornbill pointed out that https://github.com/jellyfin/jellyfin/security/advisories/GHSA-rgjw-4fwc-9v96 existed in the matrix dev. channel, so the new solution must be resistant to this attack vector. @cvium mentioned in https://github.com/jellyfin/jellyfin/pull/12074#issuecomment-2174448293 > […] Ideally, and this is just my opinion: > > 1. All images are proxied through the Jellyfin backend > > 2. Identify (and other remote download flows?) stores an ID per image > > 3. Remote image requests always use the ID and based on the ID, the server knows what to download and store > > > EDIT: This is a pretty big API breaking change, but ultimately the only reasonable (imo) fix for the security advisory. To ease clients into it, we could create a new set of endpoints. This issue exists to find the best way to solve it if cvium's solution is not enough, or for somebody qualified to undertake the "pretty big API breakage change" to have a reference and/or reminder that it needs fixing at some point. ### Reproduction Steps **Context**: Install an image provider that uses local images fetched from another local server. Make sure Jellyfin is using one route to the server that it's clients cannot resolve, and versa visa. Images returned from the provider is directly resolvable by Jellyfin's clients, but not by Jellyfin itself as a result, but the provider is able to resolve the image for Jellyfin. **What works**: 1. Open any entity in jellyfin. 2. Remove any existing images on the entity. 3. Click "Refresh metadata", then "Search for missing metadata" or "Replace all metadata", and **make sure** to enable "Replace existing images", to force it to download images. 4. Let it run. 5. No errors in the UI, and the log is not filled with errors 6. The entity have images again. **What doesn't work**: 1. Open any entity in Jellyfin 2. Remove any existing images on the entity. 3. Open "Edit Images", followed by searching for new images. Observe that the images are properly displaying in the "Search" modal. 4. Press "Download" on any of the images. 5. It errs out on the server side and the UI doesn't properly get rid of the loading animation. 6. Refresh to see that you still don't have images in the UI (since it erred out). ### Jellyfin Version 10.9.0 ### if other: _No response_ ### Environment ```markdown - OS: All - Linux Kernel: none - Virtualization: none - Clients: All - Browser: none - FFmpeg Version: N/A - Playback Method: none - Hardware Acceleration: none - GPU Model: none - Plugins: Any - Reverse Proxy: none - Base URL: none - Networking: Any - Storage: Any ``` ### Jellyfin logs ```shell [2024-06-10 17:24:17.925 -04:00] [ERR] [32] Jellyfin.Api.Middleware.ExceptionMiddleware: Error processing request. URL "POST" "/Items/afddb0774e997c04cbee139da8304d84/RemoteImages/Download". System.Net.Http.HttpRequestException: Response status code does not indicate success: 404 (Not Found). at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode() at MediaBrowser.Providers.Manager.ProviderManager.SaveImage(BaseItem item, String url, ImageType type, Nullable`1 imageIndex, CancellationToken cancellationToken) at Jellyfin.Api.Controllers.RemoteImageController.DownloadRemoteImage(Guid itemId, ImageType type, String imageUrl) at lambda_method1269(Closure, Object) at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Jellyfin.Api.Middleware.ServerStartupMessageMiddleware.Invoke(HttpContext httpContext, IServerApplicationHost serverApplicationHost, ILocalizationManager localizationManager) at Jellyfin.Api.Middleware.WebSocketHandlerMiddleware.Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) at Jellyfin.Api.Middleware.IPBasedAccessValidationMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager) at Jellyfin.Api.Middleware.LanFilteringMiddleware.Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Jellyfin.Api.Middleware.QueryStringDecodingMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.ReDoc.ReDocMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext) at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Jellyfin.Api.Middleware.RobotsRedirectionMiddleware.Invoke(HttpContext httpContext) at Jellyfin.Api.Middleware.LegacyEmbyRouteRewriteMiddleware.Invoke(HttpContext httpContext) at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.InvokeCore(HttpContext context) at Jellyfin.Api.Middleware.ResponseTimeMiddleware.Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager) at Jellyfin.Api.Middleware.ExceptionMiddleware.Invoke(HttpContext context) ``` ### FFmpeg logs _No response_ ### Please attach any browser or client logs here _No response_ ### Please attach any screenshots here _No response_ ### Code of Conduct - [x] I agree to follow this project's Code of Conduct
backuprepo added the
confirmed
bug
labels 2025-12-22 03:27:19 +01:00
Author
Owner

@jellyfin-bot commented on GitHub (Oct 19, 2024):

This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs.

If you have any questions you can use one of several ways to contact us.

@jellyfin-bot commented on GitHub (Oct 19, 2024): This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs. If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: starred/jellyfin#6038
No description provided.