Skip to content
Helpdesk & Ticketing

A Bug Report That Didn't Quite Reproduce, and Why We Fixed It Anyway

A researcher reported a stored-XSS path through PDF attachments in FanDesk tickets. The exact path likely didn't reproduce as described — we hardened the code anyway.

H
Hamze Zare Nasiri
September 16, 2026

A researcher named Dhruv emailed us to report a stored XSS path: someone attaches a PDF with embedded JavaScript to a FanDesk support ticket, and when a support agent opens it, the script runs "in the context of the application."

What we found when we checked

The download route in FanDesk has forced Content-Disposition: attachment and application/octet-stream since December 2025 — we checked with git blame. The frontend attachment viewer has zero iframe, embed, or object anywhere in the code; every non-image attachment goes through a Blob download, never an inline render. Uploads are already validated with python-magic, reading the real file bytes instead of trusting the browser's claimed content type, and image/svg+xml is explicitly excluded from the allow-list for exactly this reason — an SVG can carry a <script> tag.

So the exact path Dhruv described likely doesn't reproduce as written. That's still not a reason to leave the code as it was.

What we changed anyway

Upload-time validation only tells you what a file was when someone uploaded it. Nothing stops the stored bytes from being served differently later if some other code path changes. We closed that gap by re-detecting the real MIME type at download time too, from the same bytes on disk, not the type stored in the database:

detected_type = magic.from_file(str(file_path), mime=True)
is_image = FileStorageService.renders_inline_safely(detected_type)

Only an allow-listed image type is ever served as inline; every PDF, and everything we don't explicitly recognize, is forced to download, now with X-Content-Type-Options: nosniff and a Content-Security-Policy: default-src 'none'; sandbox on the response, so a browser can't decide otherwise even if some future code path opens the file directly. We added four tests around this logic and shipped it to production the same day.

The actual lesson

A bug report that doesn't reproduce exactly as written is still worth reading carefully. Dhruv's email pointed at a real gap — not an exploitable one today, but a gap between what we validate at upload and what we trust at download. Closing that gap cost an afternoon. Finding out we needed to cost someone else's careful attention, and earned a reply thanking them for it.

Need a Helpdesk That Takes Security Seriously?

FanDesk is enterprise helpdesk software with native RTL Persian, business-hour SLAs, and a self-hosted option — built and hardened by the same team running it in production.

View FanDesk

Share This Article