August 31, 2026
The PDF Link That Wasn’t What It Seemed: A Quick Look at a Firefox PDF.js Spoofing Bug
Phishing rarely needs a zero-day. Most of the time, it just needs one honest-looking string that turns out to be lying. That’s exactly what…
By ketu
2 min read
Phishing rarely needs a zero-day. Most of the time, it just needs one honest-looking string that turns out to be lying. That's exactly what happened in a recently fixed Firefox bug involving PDF.js, the built-in PDF viewer that ships with every copy of Firefox.
The Setup
PDF.js renders annotation links (the clickable hyperlinks embedded in PDF files) using the raw URL string found in the PDF itself. That's normally fine. The problem shows up when the URL is crafted like this:
https://trusted.example@attacker.example/pathhttps://trusted.example@attacker.example/pathIf you know a bit about URL syntax, you'll recognize trusted.example here isn't a domain at all: it's userinfo, the optional "username" portion of a URL that appears before the @ symbol. The actual host the browser will navigate to is attacker.example. Browsers have understood this distinction for decades. Most users have not.
Why It Mattered
When PDF.js rendered the tooltip for this link, it displayed the full raw string, trusted.example@attacker.example, rather than resolving and surfacing the real destination. To someone hovering over the link, it looks like they're about to visit trusted.example. In reality, clicking sends them straight to attacker.example.
This is a classic userinfo-based hostname spoofing pattern, and it's been used in phishing campaigns against ordinary web pages for years. Most browsers today mitigate it in the address bar and status bar. The twist here is that PDF.js is bundled into the browser itself. Unlike a link on some random website, users implicitly extend a higher level of trust to anything rendered by their browser's built-in viewer.
The Pushback
When the bug was first reported, it didn't get an immediate "yes, that's a vulnerability." A Mozilla engineer noted that, in principle, any website already has significant control over how its own links are displayed, so is this really PDF.js's fault?
I pushed back with a point: PDF.js isn't "any website." It's part of the browser chrome experience, and users reasonably expect a document opened inside their browser to represent links accurately, especially given that the browser's own status bar is one of the few remaining trustworthy signals left when phishing is on the table.
Mozilla's security team ultimately agreed that although the browser's hover text at the bottom of the page correctly showed the true destination (https://attacker.example/path), and so a careful user wasn't actually being deceived by the browser chrome, the fact that PDF.js displayed the untrusted, unparsed string invited confusion. The bug was confirmed, tagged as a spoofing issue, and classified as S3 severity (a real but non-critical security concern).
The Fix
The resolution was simple and exactly what security researchers usually ask for: strip the userinfo component from the URL before rendering it. Instead of showing users a string engineered to look like a trusted domain, PDF.js now displays (or at least won't cosmetically mislead based on) the canonicalized URL, with the username@ prefix removed.
The fix landed as a small, targeted pull request and was merged into Firefox's PDF.js codebase, shipping in the Firefox 152 branch.
The Bigger Lesson
This bug is a good reminder of a few things that show up again and again in security work:
- Trust boundaries matter more than technical correctness. Nothing about this URL was "invalid": it's syntactically legal. The problem was entirely about what a user perceives versus what actually happens.
- Built-in tooling inherits extra trust, and extra responsibility. A regular webpage spoofing its own links is old news. A trusted, first-party component doing the same thing, even passively, by just not sanitizing what it displays, is a different story.
- Small UI details are attack surface. No exploit chain, no memory corruption, no clever bypass. Just an unparsed string and a tooltip. That's often all a phishing attack needs.
- Defense in depth matters. Even though the browser's own status bar showed the correct destination, that safety net doesn't help if users don't check it. The fix at the PDF.js layer removes the deceptive display in the first place.
It's a small bug, but it's the kind of small bug that phishing kits love to find.
Bug tracked as Mozilla Bug 2025109. Reported by an external security researcher and resolved by Mozilla engineer Calixte Denizet. Fixed in Firefox 152.
References:
- Bugzilla report: https://bugzilla.mozilla.org/show_bug.cgi?id=2025109
- Fix (GitHub PR): https://github.com/mozilla/pdf.js/pull/21128