Attachment 'thumbnails' are the full-size original image — no actual resized thumbnail is ever generated #98
Closed
opened 2026-09-01 00:13:38 -04:00 by jared
·
1 comment
No Branch/Tag Specified
main
development
fix/comment-markdown-persist-18
deploy-2026.10.03-279
deploy-2026.10.03-275
deploy-2026.09.26-272
deploy-2026.09.25-268
deploy-2026.09.25-264
deploy-2026.09.24-258
deploy-2026.09.24-254
deploy-2026.09.12-242
deploy-2026.09.12-238
deploy-2026.09.12-234
deploy-2026.09.12-230
deploy-2026.09.12-226
deploy-2026.09.12-222
deploy-2026.09.11-218
deploy-2026.09.11-214
deploy-2026.09.11-210
deploy-2026.09.11-206
deploy-2026.09.11-202
deploy-2026.09.11-198
deploy-2026.09.11-194
deploy-2026.09.11-190
deploy-2026.09.11-186
deploy-2026.09.09-182
deploy-2026.09.09-178
deploy-2026.09.08-172
deploy-2026.09.01-163
deploy-2026.08.08-155
deploy-2026.08.08-151
deploy-2026.08.08-147
deploy-2026.08.08-143
deploy-2026.08.08-139
deploy-2026.07.15-130
deploy-2026.07.15-122
deploy-2026.07.11-107
deploy-2026.06.30-96
deploy-2026.06.30-92
deploy-2026.06.30-88
deploy-2026.06.30-84
deploy-2026.06.30-80
deploy-2026.06.30-76
deploy-2026.06.30-72
deploy-2026.06.30-68
deploy-2026.04.29-49
deploy-2026.04.29-41
deploy-2026.04.18-35
deploy-2026.04.16-31
deploy-2026.04.16-27
deploy-2026.04.16-23
deploy-2026.04.16-11
deploy-2026.04.14-9
Labels
Clear labels
api
bug
concurrency
config
data-integrity
dead-code
documentation
duplicate
enhancement
help wanted
invalid
needs-decision
notifications
performance
priority/docs
priority/high
priority/low
priority/medium
question
rate-limiting
reliability
security
ux
wontfix
workflow
Bearer/internal API surface
Something is not working
Race condition / concurrency bug
Configuration / deployment default
Data correctness / schema / integrity issue
Unused / dead code cleanup
README / docs accuracy
This issue or pull request already exists
New feature
Need some help
Something is wrong
Needs a maintainer decision, not clearly a bug
Matrix / in-app notification bug
Performance or resource-usage concern
Documentation-only gap
High-severity / high-impact issue
Low-severity / cosmetic issue
Medium-severity issue
More information is needed
Rate limiting behavior
Reliability / error-handling gap
Security or access-control impact
User-facing UX/functional bug
This won't be fixed
Ticket status workflow engine
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: LotusGuild/tinker_tickets#98
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: Medium (performance/UX)
assets/js/ticket.js(~lines 1052-1057) points both the grid<img>thumbnail and the lightbox<a href>at the samedownload_attachment.php?...&inline=1URL serving the original file. There is no server-side image resizing anywhere in the app.Impact: A multi-MB photo attachment costs a full multi-MB download just to render a small preview thumbnail in the grid. Opening the attachments tab on a ticket with several large photos is bandwidth- and load-time-expensive;
loading="lazy"only defers off-screen images, it doesn't reduce per-image transfer size.Fix: Generate a resized thumbnail (e.g. GD
imagecreatefromstring()+ resize, with an explicit dimension/pixel-count cap before processing to guard against a decompression-bomb-style crafted image causing memory exhaustion) at upload time, and serve that for the grid view — reserve the full-size original for the lightbox/actual download.Fixed in commit
dcf9b0c(merged to main in786674a).The attachment grid's
thumbnail pointed at the same download_attachment.php URL as the full-size original, so previewing a multi-MB photo attachment cost a full multi-MB download just to render a small grid preview.
Fix: generate a resized JPEG thumbnail (longest side capped at 300px) via GD at upload time, from the same metadata-stripped image stripImageMetadata() already produces, reusing its decompression-bomb guard (~40MP decode cap). The thumbnail's filename is stored in a new nullable ticket_attachments.thumbnail_filename column (migration 005); NULL means no thumbnail exists (non-image, GD unavailable, or an attachment predating this change) and callers fall back to the full-size original.
download_attachment.php serves the thumbnail via ?thumb=1 when one exists; the attachments grid now requests it for the
preview, while the lightbox link is unchanged and still opens the full-size original. delete_attachment.php removes the thumbnail alongside the original, and cleanup_orphan_uploads.php's orphan lookup now also matches thumbnail_filename so generated thumbnails aren't swept up as orphans.
Verified against real MariaDB + GD: a 1600x1200 test JPEG produced a 300x225 thumbnail at ~1.8KB vs. the 52KB original (~29x smaller); confirmed the serving logic picks the thumbnail for image attachments with one, falls back to the original for a non-image attachment even when thumb=1 is requested, and that the updated orphan-cleanup lookup matches both the original and thumbnail filename correctly.