Skip to main content

Guides

Homoglyph Attacks: How to Detect Lookalike Unicode Characters

CleanPastedText editorial · Updated

Text workbench

Try it on your text

Cleaning mode

Removes hidden characters and standardizes AI-style punctuation.

Try a real example

Each sample contains a problem you cannot see.

Original

Pasted text

0 chars · 0 words

Cleaned

Ready to copy

0 changes

Text stays in this browser

Same words · No AI rewriting · No content logging

What is a homoglyph attack?

A homoglyph is a character that looks the same as, or nearly the same as, a different character. A homoglyph attack exploits that overlap by substituting one or more letters in a string with their lookalikes from another script — most often swapping a Latin letter for its Cyrillic or Greek twin. Cyrillic а (U+0430) and Latin a (U+0061) render as the same glyph in almost every font, but they are different code points: to a human eye the text is identical, to a computer comparing bytes it is a completely different string.

That gap is what makes homoglyphs dangerous. Anywhere a system relies on an exact string match — comparing a domain name, checking a username against a blocklist, matching an email address, flagging plagiarized text — a single substituted character defeats the check while leaving the text looking untouched to the person reading it. This is a distinct problem from the invisible characters this site normally deals with: those have no glyph at all and can be safely deleted; a homoglyph is a real, visible letter, and deleting or replacing it automatically risks destroying genuine text in another script.

The 2005 PayPal exploit that started it

The attack has a precise starting date. Security researcher Eric Johanson of The Shmoo Group notified browser vendors on January 19, 2005, then publicly disclosed at ShmooCon on February 6, 2005 (covered the same week by security researcher Bruce Schneier) that Firefox 1.0, Safari 1.2.5, and Opera 7.54 rendered the domain xn--pypal-4ve.com — registered with a Cyrillic а standing in for the Latin a in "paypal" — identically to the real paypal.com in the address bar. This was the first practical demonstration that internationalized domain names (IDNs), which let browsers display non-Latin scripts natively instead of raw Punycode, could be weaponized for phishing. ICANN issued a public statement within days, and Mozilla shipped a fix that restricted which top-level domains were allowed to render as Unicode at all — everything else fell back to visible Punycode (the xn-- prefix), which at least exposes the trick to anyone who looks. The underlying bug report is still public on Mozilla's Bugzilla, filed as bug 279099.

The 2017 all-Cyrillic apple.com attack

Mozilla's 2005 fix checked for mixed scripts within one label — Latin letters next to Cyrillic letters in the same word — but it didn't stop a label written entirely in one foreign script. In April 2017, developer Xudong Zheng showed exactly that gap in a post titled "Phishing with Unicode Domains": he registered a domain where every single letter of "apple.com" was replaced by its Cyrillic counterpart. Because the label was single-script — all Cyrillic, no mixing — it passed the 2005-era check and rendered pixel-for-pixel as apple.com in Chrome, Firefox, and Opera. Zheng reported it privately in January 2017; Chrome shipped a fix in version 58 (March 2017) and Firefox followed, both adding a check against Unicode Technical Standard 39's confusable-character tables on top of the mixed-script rule, not just within a single label.

How browsers detect homoglyphs today

Modern homoglyph defense in Chrome and Firefox both trace back to one standard: Unicode Technical Standard 39, "Unicode Security Mechanisms". It defines two checks browsers now run on every domain label before deciding whether to display it in Unicode or fall back to Punycode:

Mixed-script check

A label combining scripts that don't normally appear together — Latin letters mixed with Cyrillic or Greek in the same word — is rejected outright and shown as Punycode. This is what the 2005 fix introduced.

Skeleton / confusable check

Even a single-script label is mapped to a canonical "skeleton" — each character reduced to its representative confusable form — and compared against known domains. A skeleton match to a real domain forces Punycode display. This closed the 2017 all-Cyrillic gap.

The full mapping browsers use lives in Unicode's confusables.txt data file, part of UTS 39, which associates thousands of characters across scripts with the ASCII or common-script character they visually resemble. It's the same reference table security tools, registrars, and plagiarism checkers draw on when they flag look-alike substitutions.

Homoglyphs beyond domains: code, documents, and pasted text

Domains are the best-documented case, but the same substitution works anywhere text is compared for equality. A Cyrillic character in a variable name compiles as a different identifier than its Latin twin, producing a bug that looks correct in every code review. A homoglyph in a submitted document defeats naive plagiarism matching. Copy-pasting from a non-English source can introduce one by accident with no attack intended at all. A few of the common pairs:

Looks likeGenuine (Latin)HomoglyphScript
aU+0061U+0430Cyrillic а
eU+0065U+0435Cyrillic е
oU+006FU+043ECyrillic о
pU+0070U+0440Cyrillic р
O (capital)U+004FU+039FGreek Ο (Omicron)

This is also what Turnitin's integrity Flags are built to catch in submitted work — the same "replaced characters" check that looks for a Cyrillic а inside otherwise-English text, covered in more detail on the hidden character checker page. For code, JSON, and CSV specifically, see sanitizing copied text for code, JSON, and CSV — homoglyphs sit alongside BiDi override characters as one of the visible-but-deceptive tricks that plain text search won't catch.

Detection, not deletion

Homoglyphs need a fundamentally different fix than the hidden characters this site otherwise covers. A zero-width space has no legitimate reading in most contexts, so removing it is safe by default. A homoglyph is a real letter that might be entirely legitimate — Cyrillic, Greek, and Latin text all coexist on the same page constantly — so the correct response is to surface it for a human to judge, never to silently swap it. That's why CleanPastedText, which strips invisible characters in your browser, does not touch homoglyphs: the safest tools here are inspection tools — a Unicode-aware code editor, a codepoint viewer, or your browser's own address bar, which since 2005 has been quietly doing exactly this check every time you click a link.

Common questions

Frequently asked questions

What is a homoglyph attack?

A homoglyph attack substitutes a character in a piece of text — usually a domain name, username, or filename — with a different Unicode character that renders almost identically. The classic example is replacing the Latin letter a (U+0061) with the Cyrillic letter а (U+0430), which most fonts draw as the same shape. The two strings look alike to a person but are different sequences of bytes to a computer, so an exact-match security check (a blocklist, a domain comparison, a duplicate-content filter) fails to catch the substitution.

What was the first IDN homograph attack?

Security researcher Eric Johanson of The Shmoo Group notified browser vendors on January 19, 2005, and publicly disclosed at ShmooCon on February 6, 2005, that Firefox 1.0, Safari 1.2.5, and Opera 7.54 would display the domain xn--pypal-4ve.com — registered using a Cyrillic а in place of the Latin a — identically to paypal.com in the address bar. ICANN issued a statement within days, and Mozilla shipped a fix restricting which top-level domains could render Unicode at all, falling back to Punycode (the xn-- form) everywhere else.

Is the 2017 Cyrillic apple.com attack still possible?

Not the same way. In April 2017 Xudong Zheng showed that registering a domain entirely in Cyrillic characters — every letter substituted, not just one — passed the mixed-script checks browsers had used since 2005, so Chrome, Firefox, and Opera rendered it identically to apple.com. Chrome and Firefox fixed this specific bypass by 2017 (Chrome in version 58) by adding whole-script confusable detection on top of the mixed-script check, per Unicode Technical Standard 39. Registrars and detection tools have closed the gap this attack used, but the underlying visual-confusability problem UTS 39 addresses is permanent — it moves to whichever surface doesn't check for it yet.

How do browsers detect homoglyph domains today?

Chrome and Firefox both implement Unicode Technical Standard 39 (Unicode Security Mechanisms). Each label of a domain is checked for mixed scripts (Latin mixed with Cyrillic in one word is blocked), and separately checked against a 'skeleton' — a canonical form where every character maps to a representative confusable — compared against a list of top domains. A label that fails either check is shown as Punycode (xn--...) instead of the deceptive Unicode text, so the address bar reveals the trick instead of hiding it.

Can CleanPastedText detect homoglyphs?

No, and this tool does not claim to. CleanPastedText removes hidden, invisible characters — ones with no visible glyph, like zero-width spaces or narrow no-break spaces — where deleting them is unambiguous because there is nothing on screen to lose. A homoglyph is a visible letter; a Cyrillic а might be a genuine Russian word, a spoofed domain, or a copy-paste accident, and only context tells you which. Swapping it automatically risks silently corrupting real non-Latin text, so this is a detection problem, not a cleaning one — see the tools below.

How can I check text or a domain for homoglyphs manually?

For a domain, hover the link or check the browser address bar: a genuine domain renders in your script; a spoofed one either shows Punycode (xn--) or, once you select and copy it, pastes as visibly foreign characters when checked in a Unicode-aware tool. For pasted text or code, a character-by-character Unicode inspector (browser dev tools, a hex/codepoint viewer, or Python's unicodedata module) reveals the actual code point behind every glyph — the fix is inspection, not automated substitution.

Continue reading

Related guides & tools