High severityNVD Advisory· Published Nov 6, 2025· Updated Apr 15, 2026
CVE-2025-64178
CVE-2025-64178
Description
Jellysweep is a cleanup tool for the Jellyfin media server. In versions 0.12.1 and below, /api/images/cache, used to download media posters from the server, accepted a URL parameter that was directly passed to the cache package, which downloaded the poster from this URL. This URL parameter can be used to make the Jellysweep server download arbitrary content. The API endpoint can only be used by authenticated users. This issue is fixed in version 0.13.0.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/jon4hz/jellysweepGo | < 0.13.0 | 0.13.0 |
Patches
1174663125109fix: use image cache again (#179)
10 files changed · +72 −79
internal/api/handler/handler.go+4 −4 modified@@ -129,14 +129,14 @@ func (h *Handler) RequestKeepMedia(c *gin.Context) { // ImageCache serves cached images or downloads them if not cached. func (h *Handler) ImageCache(c *gin.Context) { - imageURL := c.Query("url") - if imageURL == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "url parameter is required"}) + mediaID, err := parseUintParam(c.Query("id")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid or missing id parameter"}) return } // Serve the cached image - err := h.engine.GetImageCache().ServeImage(c.Request.Context(), imageURL, c.Writer, c.Request) + err = h.engine.GetImageCache().ServeImage(c.Request.Context(), mediaID, c.Writer, c.Request) if err != nil { // Error is already handled in ServeImage return
internal/api/models/converter.go+0 −2 modified@@ -12,7 +12,6 @@ func ToUserMediaItem(m database.Media, cfg *config.Config) UserMediaItem { ID: m.ID, Title: m.Title, Year: m.Year, - PosterURL: m.PosterURL, MediaType: MediaType(m.MediaType), LibraryName: m.LibraryName, FileSize: m.FileSize, @@ -58,7 +57,6 @@ func ToAdminMediaItem(m database.Media, cfg *config.Config) AdminMediaItem { TvdbId: m.TvdbId, Year: m.Year, FileSize: m.FileSize, - PosterURL: m.PosterURL, MediaType: MediaType(m.MediaType), RequestedBy: m.RequestedBy, DefaultDeleteAt: m.DefaultDeleteAt,
internal/api/models/models.go+0 −2 modified@@ -24,7 +24,6 @@ type UserMediaItem struct { ID uint `json:"ID"` Title string `json:"Title"` Year int32 `json:"Year"` - PosterURL string `json:"PosterURL"` MediaType MediaType `json:"MediaType"` LibraryName string `json:"LibraryName"` FileSize int64 `json:"FileSize"` @@ -55,7 +54,6 @@ type AdminMediaItem struct { TvdbId *int32 `json:"TvdbId,omitempty"` Year int32 `json:"Year"` FileSize int64 `json:"FileSize"` - PosterURL string `json:"PosterURL"` MediaType MediaType `json:"MediaType"` RequestedBy string `json:"RequestedBy"` DefaultDeleteAt time.Time `json:"DefaultDeleteAt"`
internal/cache/image_cache.go+19 −14 modified@@ -13,18 +13,20 @@ import ( "github.com/charmbracelet/log" "github.com/disintegration/imaging" + "github.com/jon4hz/jellysweep/internal/database" ) type ImageCache struct { cacheDir string client *http.Client + db database.MediaDB maxWidth int // Maximum width for scaled images maxHeight int // Maximum height for scaled images quality int // JPEG quality (1-100) } // NewImageCache creates a new image cache manager with scaling options. -func NewImageCache(cacheDir string) *ImageCache { +func NewImageCache(cacheDir string, db database.MediaDB) *ImageCache { // Create cache directory if it doesn't exist if err := os.MkdirAll(cacheDir, 0o755); err != nil { //nolint:gosec log.Errorf("Failed to create cache directory: %v", err) @@ -38,6 +40,7 @@ func NewImageCache(cacheDir string) *ImageCache { client: &http.Client{ Timeout: 30 * time.Second, }, + db: db, } } @@ -192,29 +195,31 @@ func (ic *ImageCache) GetCachedImageURL(imageURL string) string { } // ServeImage serves a cached image or downloads it if not cached. -func (ic *ImageCache) ServeImage(ctx context.Context, imageURL string, w http.ResponseWriter, r *http.Request) error { - if imageURL == "" { +func (ic *ImageCache) ServeImage(ctx context.Context, mediaID uint, w http.ResponseWriter, r *http.Request) error { + if mediaID == 0 { http.NotFound(w, r) return nil } - cacheFilePath, err := ic.GetCachedImagePath(ctx, imageURL) + media, err := ic.db.GetMediaItemByID(ctx, mediaID) + if err != nil { + log.Errorf("Failed to get media item: %v", err) + http.NotFound(w, r) + return err + } + + if media.PosterURL == "" { + http.NotFound(w, r) + return nil + } + + cacheFilePath, err := ic.GetCachedImagePath(ctx, media.PosterURL) if err != nil { log.Errorf("Failed to get cached image: %v", err) http.Error(w, "Failed to get image", http.StatusInternalServerError) return err } - /* // make sure the path is absolute. This should never be false because we hash the image URL from http request. - // But owasp and other scanners complain about possible relative paths. - if !filepath.IsAbs(imageURL) { - log.Errorf("Invalid image URL: %s", imageURL) - http.Error(w, "Invalid image URL", http.StatusBadRequest) - return fmt.Errorf("invalid image URL: %s", imageURL) - } */ - // Doesnt work an breaks some url. - // TODO: maybe predownload all images when we get the sonarr items and then serve them from the cache? - // Open the cached file file, err := os.Open(cacheFilePath) if err != nil {
internal/engine/arr/arr.go+0 −12 modified@@ -3,8 +3,6 @@ package arr import ( "context" "errors" - "fmt" - "net/url" "time" "github.com/devopsarr/radarr-go/radarr" @@ -48,13 +46,3 @@ type JellyfinItem struct { } var ErrRequestAlreadyProcessed = errors.New("request already processed") - -// GetCachedImageURL converts a direct image URL to a cached URL. -func GetCachedImageURL(imageURL string) string { - if imageURL == "" { - return "" - } - // Encode the original URL and return a cache endpoint URL - encoded := url.QueryEscape(imageURL) - return fmt.Sprintf("/api/images/cache?url=%s", encoded) -}
internal/engine/engine.go+1 −1 modified@@ -180,7 +180,7 @@ func New(cfg *config.Config, db database.DB, initialDBMigration bool) (*Engine, data: &data{ userNotifications: make(map[string][]arr.MediaItem), }, - imageCache: cache.NewImageCache("./data/cache/images"), + imageCache: cache.NewImageCache("./data/cache/images", db), cache: engineCache, }
web/templates/components/admin-media-grid.templ+12 −11 modified@@ -313,7 +313,6 @@ script AdminKeepRequestGridScript() { type: request.MediaType, year: request.Year, library: request.LibraryName, - posterURL: request.PosterURL, deletionTimestamp: deletionTimestamp, canRequest: false, // Already requested hasRequested: true, // By definition @@ -374,8 +373,10 @@ script AdminKeepRequestGridScript() { const deletionDate = new Date(item.deletionTimestamp); const deletionTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + // Use image cache API endpoint + const posterUrl = item.id ? `/api/images/cache?id=${item.id}` : ''; + const posterImg = posterUrl + ? `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` : `<div class="w-full h-64 bg-gray-700 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -458,8 +459,8 @@ script AdminKeepRequestGridScript() { <div class="card"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + ${posterUrl + ? `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` : `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -735,7 +736,6 @@ script AdminKeepRequestGridScript() { id: item.ID || item.id, title: item.Title || item.title, library: item.LibraryName || item.library, - posterURL: item.PosterURL || item.posterURL, fileSize: parseInt(item.FileSize || item.fileSize || 0), deletionTimestamp: deletionTimestamp, hasRequested: !!(item.Request && item.Request.ID) || !!item.hasRequested, // Check if Request exists and has ID @@ -840,7 +840,6 @@ script AdminMediaGridScript() { type: item.MediaType, year: item.Year, library: item.LibraryName, - posterURL: item.PosterURL, deletionTimestamp: item.DefaultDeleteAt ? new Date(item.DefaultDeleteAt).getTime() : 0, // database.Media uses DefaultDeleteAt fileSize: item.FileSize || 0, hasRequested: !!(item.Request && item.Request.ID), // Check if Request exists and has ID @@ -910,8 +909,10 @@ script AdminMediaGridScript() { const deletionDate = new Date(item.deletionTimestamp); const relativeTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + // Use image cache API endpoint + const posterUrl = item.id ? `/api/images/cache?id=${item.id}` : ''; + const posterImg = posterUrl + ? `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` : `<div class="w-full h-64 bg-gray-700 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -1031,8 +1032,8 @@ script AdminMediaGridScript() { <div class="card"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + ${posterUrl + ? `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` : `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path>
web/templates/components/admin-media-grid_templ.go+20 −19 modified@@ -415,8 +415,8 @@ func getAdminMediaUniqueTypes(items []models.AdminMediaItem) []models.MediaType func AdminKeepRequestGridScript() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_AdminKeepRequestGridScript_1784`, - Function: `function __templ_AdminKeepRequestGridScript_1784(){class AdminKeepRequestGridManager extends MediaGridManager { + Name: `__templ_AdminKeepRequestGridScript_044b`, + Function: `function __templ_AdminKeepRequestGridScript_044b(){class AdminKeepRequestGridManager extends MediaGridManager { constructor(containerId, options = {}) { super(containerId, options); } @@ -463,7 +463,6 @@ func AdminKeepRequestGridScript() templ.ComponentScript { type: request.MediaType, year: request.Year, library: request.LibraryName, - posterURL: request.PosterURL, deletionTimestamp: deletionTimestamp, canRequest: false, // Already requested hasRequested: true, // By definition @@ -524,8 +523,10 @@ func AdminKeepRequestGridScript() templ.ComponentScript { const deletionDate = new Date(item.deletionTimestamp); const deletionTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` + // Use image cache API endpoint + const posterUrl = item.id ? ` + "`" + `/api/images/cache?id=${item.id}` + "`" + ` : ''; + const posterImg = posterUrl + ? ` + "`" + `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-full h-64 bg-gray-700 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -608,8 +609,8 @@ func AdminKeepRequestGridScript() templ.ComponentScript { <div class="card"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` + ${posterUrl + ? ` + "`" + `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -885,7 +886,6 @@ func AdminKeepRequestGridScript() templ.ComponentScript { id: item.ID || item.id, title: item.Title || item.title, library: item.LibraryName || item.library, - posterURL: item.PosterURL || item.posterURL, fileSize: parseInt(item.FileSize || item.fileSize || 0), deletionTimestamp: deletionTimestamp, hasRequested: !!(item.Request && item.Request.ID) || !!item.hasRequested, // Check if Request exists and has ID @@ -944,15 +944,15 @@ func AdminKeepRequestGridScript() templ.ComponentScript { } }); }`, - Call: templ.SafeScript(`__templ_AdminKeepRequestGridScript_1784`), - CallInline: templ.SafeScriptInline(`__templ_AdminKeepRequestGridScript_1784`), + Call: templ.SafeScript(`__templ_AdminKeepRequestGridScript_044b`), + CallInline: templ.SafeScriptInline(`__templ_AdminKeepRequestGridScript_044b`), } } func AdminMediaGridScript() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_AdminMediaGridScript_9916`, - Function: `function __templ_AdminMediaGridScript_9916(){class AdminMediaGridManager extends MediaGridManager { + Name: `__templ_AdminMediaGridScript_0680`, + Function: `function __templ_AdminMediaGridScript_0680(){class AdminMediaGridManager extends MediaGridManager { constructor(containerId, options = {}) { super(containerId, options); } @@ -996,7 +996,6 @@ func AdminMediaGridScript() templ.ComponentScript { type: item.MediaType, year: item.Year, library: item.LibraryName, - posterURL: item.PosterURL, deletionTimestamp: item.DefaultDeleteAt ? new Date(item.DefaultDeleteAt).getTime() : 0, // database.Media uses DefaultDeleteAt fileSize: item.FileSize || 0, hasRequested: !!(item.Request && item.Request.ID), // Check if Request exists and has ID @@ -1066,8 +1065,10 @@ func AdminMediaGridScript() templ.ComponentScript { const deletionDate = new Date(item.deletionTimestamp); const relativeTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` + // Use image cache API endpoint + const posterUrl = item.id ? ` + "`" + `/api/images/cache?id=${item.id}` + "`" + ` : ''; + const posterImg = posterUrl + ? ` + "`" + `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-full h-64 bg-gray-700 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -1187,8 +1188,8 @@ func AdminMediaGridScript() templ.ComponentScript { <div class="card"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` + ${posterUrl + ? ` + "`" + `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -1573,8 +1574,8 @@ func AdminMediaGridScript() templ.ComponentScript { } }); }`, - Call: templ.SafeScript(`__templ_AdminMediaGridScript_9916`), - CallInline: templ.SafeScriptInline(`__templ_AdminMediaGridScript_9916`), + Call: templ.SafeScript(`__templ_AdminMediaGridScript_0680`), + CallInline: templ.SafeScriptInline(`__templ_AdminMediaGridScript_0680`), } }
web/templates/components/dashboard-media-grid.templ+6 −5 modified@@ -167,7 +167,6 @@ script DashboardMediaGridScript() { type: item.MediaType, // database.Media uses MediaType field year: item.Year, library: item.LibraryName, // database.Media uses LibraryName field - posterURL: item.PosterURL, deletionTimestamp: item.DefaultDeleteAt ? new Date(item.DefaultDeleteAt).getTime() : 0, // database.Media uses DefaultDeleteAt fileSize: item.FileSize || 0, hasRequested: item.Request && item.Request.ID ? true : false, // Check if Request exists and has ID @@ -212,8 +211,10 @@ script DashboardMediaGridScript() { const deletionDate = new Date(item.deletionTimestamp); const relativeTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + // Use image cache API endpoint + const posterUrl = item.id ? `/api/images/cache?id=${item.id}` : ''; + const posterImg = posterUrl + ? `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` : `<div class="w-full h-64 bg-gray-800 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -337,8 +338,8 @@ script DashboardMediaGridScript() { <div class="block sm:hidden"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + ${posterUrl + ? `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` : `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path>
web/templates/components/dashboard-media-grid_templ.go+10 −9 modified@@ -181,8 +181,8 @@ func DashboardFilters(mediaItems []models.UserMediaItem) templ.Component { func DashboardMediaGridScript() templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_DashboardMediaGridScript_9e24`, - Function: `function __templ_DashboardMediaGridScript_9e24(){class DashboardMediaGridManager extends MediaGridManager { + Name: `__templ_DashboardMediaGridScript_2b58`, + Function: `function __templ_DashboardMediaGridScript_2b58(){class DashboardMediaGridManager extends MediaGridManager { constructor(containerId, options = {}) { super(containerId, options); } @@ -226,7 +226,6 @@ func DashboardMediaGridScript() templ.ComponentScript { type: item.MediaType, // database.Media uses MediaType field year: item.Year, library: item.LibraryName, // database.Media uses LibraryName field - posterURL: item.PosterURL, deletionTimestamp: item.DefaultDeleteAt ? new Date(item.DefaultDeleteAt).getTime() : 0, // database.Media uses DefaultDeleteAt fileSize: item.FileSize || 0, hasRequested: item.Request && item.Request.ID ? true : false, // Check if Request exists and has ID @@ -271,8 +270,10 @@ func DashboardMediaGridScript() templ.ComponentScript { const deletionDate = new Date(item.deletionTimestamp); const relativeTime = this.formatRelativeTime(deletionDate); - const posterImg = item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` + // Use image cache API endpoint + const posterUrl = item.id ? ` + "`" + `/api/images/cache?id=${item.id}` + "`" + ` : ''; + const posterImg = posterUrl + ? ` + "`" + `<img src="${posterUrl}" class="w-full h-64 object-cover" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-full h-64 bg-gray-800 flex items-center justify-center"> <svg class="w-16 h-16 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -396,8 +397,8 @@ func DashboardMediaGridScript() templ.ComponentScript { <div class="block sm:hidden"> <div class="flex items-center p-4 gap-4"> <div class="shrink-0"> - ${item.posterURL - ? ` + "`" + `<img src="${item.posterURL}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` + ${posterUrl + ? ` + "`" + `<img src="${posterUrl}" alt="${item.title}" class="w-16 h-24 object-cover rounded" loading="lazy"/>` + "`" + ` : ` + "`" + `<div class="w-16 h-24 bg-gray-700 rounded flex items-center justify-center"> <svg class="w-8 h-8 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2h3a1 1 0 011 1v2a1 1 0 01-1 1h-1v12a2 2 0 01-2 2H6a2 2 0 01-2-2V8H3a1 1 0 01-1-1V5a1 1 0 011-1h3z"></path> @@ -634,8 +635,8 @@ func DashboardMediaGridScript() templ.ComponentScript { } }); }`, - Call: templ.SafeScript(`__templ_DashboardMediaGridScript_9e24`), - CallInline: templ.SafeScriptInline(`__templ_DashboardMediaGridScript_9e24`), + Call: templ.SafeScript(`__templ_DashboardMediaGridScript_2b58`), + CallInline: templ.SafeScriptInline(`__templ_DashboardMediaGridScript_2b58`), } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
0No linked articles in our index yet.