Zero-width characters: invisible text fingerprinting and how to find it

There is a family of Unicode characters that take up no space, draw nothing at all, and survive copying, reformatting and most kinds of editing. They exist for good reasons: joining emoji into one picture, shaping Persian and Hindi letters, telling a browser where it may break a very long URL. They are also the easiest way to mark a document so that a copy which leaks can be traced back to the person it was handed to.
The characters that draw nothing
The usual suspects are U+200B zero-width space, U+200C zero-width non-joiner, U+200D zero-width joiner, U+2060 word joiner, U+FEFF zero-width no-break space (the byte order mark), U+00AD soft hyphen, U+200E and U+200F left-to-right and right-to-left marks, and the directional isolates U+2066–U+2069. Unicode files most of them under the "format" category (Cf) and marks many of them Default_Ignorable_Code_Point, which asks renderers to draw nothing at all rather than a fallback box.
Because nothing is drawn, they are invisible in Word, in Google Docs, in browsers and in most editors. Selecting text with the mouse steps over them; arrow keys occasionally betray them as one extra keypress in the middle of a word. They do count toward the character count, which is one of the few honest tells you get for free.
The soft hyphen U+00AD is a special case: invisible until the line happens to wrap at that point, when it renders as a real hyphen. Text pasted from a PDF or from justified Word copy is often full of them, which is why a clean-looking paragraph suddenly grows hyphens in the middle of words after you resize the window.
Where they legitimately belong
The zero-width joiner is the glue in emoji sequences. A family emoji is several separate people joined by U+200D; a professional emoji is a person, a joiner and an object. Strip the joiners and one picture becomes three.
In Persian, Arabic, Hindi and several other scripts, the joiner and non-joiner control how letters connect to their neighbours. Removing them there is not tidying up, it is changing the word. Any filter that strips invisible characters blindly will eventually mangle someone's name.
The zero-width space marks a legitimate break opportunity inside a long URL or a German compound noun. U+FEFF at the very start of a file is a byte order mark that tells a reader which encoding follows; the same character in the middle of a paragraph is almost always an accident of concatenating two files.
How the same characters become a fingerprint
The trick is simple. Take an identifier — a recipient number, an employee id — write it in binary, and encode the bits as a choice between two invisible characters placed between words: zero-width space for 0, zero-width non-joiner for 1. Twenty or so characters are enough to identify one person out of a million, and the text on screen is byte-for-byte identical to everyone else's.
The same idea appears as canary tokens in leak investigations, as per-recipient watermarking of embargoed press releases, NDAs and internal memos, and — much more mundanely — in "copy protection" scripts that inject invisible markers into any text copied off a web page. None of it requires special software; it is a few lines of code and a loop over the words.
A related trick uses homoglyphs instead of invisible characters: a Cyrillic а (U+0430) in place of the Latin a, a Greek ο in place of an o. The text renders identically, and the pattern of substitutions is the signature. Both tricks survive copy-paste, both survive a change of font, and neither shows up in a spellchecker.
Finding them
The tool on this page marks every invisible character it finds in place and tells you its codepoint, so you can see both how many there are and where they sit. Outside the browser, ripgrep and grep will match them with an explicit escape, and VS Code highlights ambiguous and invisible characters by default — the setting is editor.unicodeHighlight.invisibleCharacters.
Word's "Show formatting marks" (Ctrl+*) displays paragraph marks, tabs and spaces, but not zero-width characters — it has no glyph to show. A hex view is decisive: a zero-width space is the three bytes E2 80 8B in UTF-8, a soft hyphen is C2 AD, and a byte order mark is EF BB BF.
A blunt check works too. Search the document for a word you can clearly see; if the search fails, something is sitting inside it. Comparing a character count against the number of characters you can actually count catches the rest.
Removing them without breaking the text
Unicode normalisation does not help. NFC and NFKC compose and decompose characters — they leave zero-width spaces, joiners, non-joiners and soft hyphens exactly where they were. If you were relying on normalisation to sanitise pasted text, it has been passing the markers through the whole time.
Removal therefore has to be explicit, and it needs one exception: the zero-width joiner between two emoji is holding a picture together. ClipboardClean removes zero-width characters, soft hyphens and direction marks from its output, but leaves a joiner alone when the characters on both sides of it are pictographic — so a family emoji survives while a fingerprint in ordinary prose does not.
If you find markers in a document you were given, assume that copy is traceable. Retyping the sensitive sentence removes them, and so does round-tripping the text through a cleaner; forwarding the file untouched does not. And if the text matters, check for homoglyphs as well — a cleaner can only remove what has no visible shape.
The takeaway
Invisible characters are ordinary Unicode doing an ordinary job — until someone uses them to tell copies of a document apart. You cannot see them, spellcheckers ignore them and normalisation keeps them, so the only reliable answer is to look for them deliberately and strip them on purpose, with an exception for the joiners that hold emoji together.
Paste your text and see what it carries
More guides

What really comes along when you paste from Word or Google Docs
Copy a paragraph out of Word and your clipboard carries a small HTML document: inline styles, MsoNormal classes, font tags and sometimes an entire stylesheet hidden in a comment. Here is what is in there and why it breaks the place you paste it.

UTM parameters, click IDs and redirect wrappers: how links track you and how to clean them
A link that reads example.com/pricing can carry twelve parameters, an identifier unique to your click and a detour through two other companies' servers. Here is what each piece does, what is safe to delete and what you should leave alone.