Three branches and an attention layer: QR phishing detection
A malicious QR code hides its URL behind a picture. That is the whole problem. A phishing link in an email can be read, hovered over, and doubted before it is clicked; the same link inside a QR code is a grid of squares until a phone has already decoded it and opened the page. The usual defence — look at where it goes before you go there — is gone.
So the detector has to work on what is available before the user commits. The question I wanted to answer was where that signal actually lives: in the URL string, in the structure of the code itself, or in the relationship between the two.
Three branches
The answer I settled on was to not choose. PhishFusion runs three encoders in parallel, one per view of the same object:
| Branch | Input | Encoder |
|---|---|---|
| Lexical | URL character sequence | Transformer encoder |
| URL | Structural URL features | MLP |
| QR | QR structural features | MLP |
The lexical branch reads the URL as a character sequence. This is where the classic phishing tells
live — brand names spliced into subdomains, character substitutions, absurd path depth, hyphen
soup. A Transformer suits it because those patterns are positional and contextual: paypal early
in a hostname means something different from paypal buried in a path segment, and self-attention
can represent that distinction directly.
The two structural branches are deliberately not sequence models. They take engineered features — counts, lengths, ratios, encoding properties — and an MLP is the right tool for a fixed-width feature vector. Handing those to a Transformer would be architecture for its own sake.
Each branch emits a 32-dimensional embedding. Three views, one common width.
Why attention instead of concatenation
The cheap way to combine three embeddings is to glue them into a 96-dimensional vector and put a classifier on top. That works, and it is the baseline any fusion model has to beat.
What it cannot do is weight the branches per sample. Concatenation learns one fixed set of weights over the three views and applies it to every input. But the views are not equally informative for every URL. A shortened link is lexically almost empty — there is nothing to read, which is itself the signal — and the structural branches have to carry the decision. A long, hand-crafted lookalike hostname is the opposite case: the lexical branch has plenty to work with.
Cross-modal attention across the three 32-dimensional embeddings lets each branch attend to the others, so the model can learn conditional relationships — a suspicious QR structure matters more when the URL is also unusual, and an unremarkable URL is less reassuring when the code it came from is malformed. The weighting is computed from the input rather than fixed at training time.
That interaction is the actual claim of the architecture. Whether it earns its complexity is an empirical question, and the honest way to answer it is single-branch and concatenation baselines measured on the same split.
Results
0.96 binary classification accuracy on an imbalanced phishing dataset.
The word doing the work in that sentence is imbalanced, and it is worth being blunt about what the number therefore does not tell you. On a skewed set, accuracy is dominated by the majority class; a model can look strong while missing a large share of the phishing it was built to catch. Precision and recall on the phishing class are the figures that decide whether the thing is useful, and they are what I would put in front of the accuracy if I were presenting this as a deployable system rather than a study.
I am stating the metric I measured, not the metric I wish I had measured. That distinction seems worth keeping.
What the setup does not cover
Two limits worth naming. The model sees a decoded URL plus structural features — it never sees the page, so a phishing site that hides behind a clean-looking domain and a redirect chain is out of scope. And QR phishing in the wild is largely a physical-world attack: stickers over legitimate codes on parking meters, restaurant tables, EV chargers. None of the context that makes those convincing is in the dataset.
A detector like this belongs in the layer that inspects a link before the browser follows it, not as the last line of defence.