Research · 6 min read · · updated

Invisible characters in AI text, and where they actually come from

A zero-width space in a draft is not the model’s signature. Here are the eight characters our tools look for, where they come from and what they break.

GPGenPolishWritten and checked in-house

In short

Invisible characters in AI text are Unicode characters such as the zero-width space, the no-break space and the byte-order mark, which change how text behaves without changing how it looks. They arrive from web pages, word processors and file encodings, not from the model. Anthropic says its text watermark adds “no hidden characters”, and reference tokenisers strip or fold them.

The belief has a shape. Someone runs a draft through a scanner, finds a character that renders nothing, and concludes that the model put it there to mark its own output. It is a reasonable guess and it is wrong, and the cleanest way to see why is to look at a company that really does watermark the text its assistant writes. Anthropic describes the mechanism plainly: “Nothing is added to the text and there are no hidden characters.”

What are the eight invisible characters?

Six of the eight invisible characters are format characters in the standard’s sense — general category Cf, and flagged Default_Ignorable_Code_Point, which is Unicode’s way of saying a renderer is entitled to draw nothing at all. The other two are not that. U+00A0 is a space, with a width; U+2028 forces a line break. Both are marked White_Space, and both are excluded from that ignorable set by construction.

So this list is not a Unicode category, and nothing in the standard groups these eight together. It is a working list: the characters that change how a text behaves without changing how it looks.

U+200BZero-width spaceRenders nothing, but splits the word for search and counting.
U+200CZero-width non-joinerBreaks a cursive join or ligature. Needed in some scripts.
U+200DZero-width joinerJoins glyphs, and holds emoji sequences together.
U+2060Word joinerPrevents a line break and leaves no visible trace.
U+FEFFByte-order markA file signature. Unicode discourages every other use.
U+00ADSoft hyphenRenders only at a line break, so it hides in plain sight.
U+00A0Non-breaking spaceLooks like a space, so a typed space will never match it.
U+2028Line separatorForces a line break. Legal, but rare in text a person typed.

Where do invisible characters come from?

Nearly every one of the eight invisible characters has an ordinary origin. Six have named HTML entities, so any text that has been through a web page can carry them without anyone typing one. The byte-order mark has a documented job at the very start of a file, where it declares the encoding, and Unicode discourages every other use of it. Word processors add non-breaking spaces on purpose: LibreOffice inserts one before a colon, semicolon, question mark or exclamation mark when the text is set in French, because that is the French typographic rule. Rich-text editors in the browser add them to stop consecutive spaces collapsing — a behaviour of the implementations, which no specification actually requires.

What does not appear to produce them is the model. The reference text tokenisers do the opposite: the one published with BERT strips every Cf character before it segments anything, and folds space characters to an ordinary space; SentencePiece normalises with NFKC, which maps U+00A0 to U+0020. Every documented step in that direction removes these characters. We could not find one that adds any.

What do invisible characters break?

The eight invisible characters do not behave alike. They behave in opposite directions, twice over.

For word segmentation, rule WB4 of the standard tells an implementation to ignore format characters inside a word, so a soft hyphen or a word joiner sitting in the middle of one leaves it intact. A zero-width space does not: its word-break property is Other, so it creates a boundary, and a single one turns one word into two for anything that counts words or searches for them. For line breaking the pattern flips, as the table shows.

Five of the eight characters, inside a word and at the end of a line
CharacterInside a wordAt a line break
Zero-width spaceSplits the word in twoOffers a break where none existed
Soft hyphenIgnored, the word stays wholePermits a break after itself
Word joinerIgnored, the word stays wholeForbids a break
No-break spaceSeparates words, as a space doesForbids a break
Line separator, 2028Separates wordsForces a break

The version people meet first is a find-and-replace that will not match. Type a space and it will not find a non-breaking space, because they are different characters that look identical. Nothing is broken, exactly; the text simply is not what it appears to be.

One case was awkward enough that the fix arrived in a language specification. Until ES2018, U+2028 and U+2029 were line terminators everywhere in ECMAScript source, string literals included, so a valid piece of JSON containing one could not be pasted into a script. ES2019 narrowed the rule to the line feed and the carriage return. JSON itself never had this problem — it has always allowed both characters unescaped, and the mismatch was on the JavaScript side.

Which characters do hidden-instruction attacks use?

Hidden-instruction attacks deserve a plain statement, because they get merged with invisible characters and the merge sells something false. The attacks you may have read about — text carrying instructions a reader cannot see, source code that reads one way and compiles another — do not use any of the eight characters above.

They use the Unicode tag block, U+E0000 to U+E007F, which can encode an entire sentence of ASCII invisibly, and the bidirectional overrides U+202A to U+202E, which reorder what a reader sees without changing what a compiler parses. Trojan Source demonstrated the second across a dozen languages and earned two CVEs. Microsoft has since documented a phishing campaign that hides tag characters inside words to defeat keyword matching, and its advice generalises well beyond phishing: normalise before you match, not after. GitHub now warns on hidden Unicode text in a diff, noting that it can make code read one way and be interpreted another — especially by an AI.

Our detector looks for none of that. It looks for eight characters that break text quietly, and it should not be mistaken for a security control.

Is an invisible character a watermark?

An AI text watermark is not stored in characters, which returns to where this started. The assistants that do watermark the text they generate do it by biasing which word the model picks, thousands of times across a passage. That signal is statistical. It is not stored in the characters, so no scanner can read it out of them, and stripping invisible characters from a draft defeats none of it. No tool that cleans characters can claim otherwise, and that includes ours. The edit that does reach such a mark is a different one: replacing the words themselves, thoroughly enough that the biased choices are no longer the model’s. That is what our rewrite does, and the editor reports the share of words it replaced. What it cannot report is what a detector would say, because the keys that read these marks are not public.

  • Normalise before you match. Search, deduplication and keyword rules should strip or fold these characters first, not afterwards.
  • Leave the zero-width joiner alone when the text contains emoji — it is what holds those sequences together, and removing it takes the sequence apart.
  • A byte-order mark at the very start of a file is doing its job. One anywhere else is not.
  • If a passage matters, check it rather than trusting that it looks right. That is the whole reason these eight are worth a tool.

Questions

Does ChatGPT add hidden characters to its answers?

ChatGPT has no documented habit of adding zero-width spaces. In April 2025 the startup Rumi reported that OpenAI’s o3 and o4-mini models were leaving narrow no-break spaces, code point 202F, in long answers. That character has a visible width, and the report is not evidence of a watermark: the assistants that do watermark text change which words the model picks.

Can removing invisible characters remove an AI watermark?

Removing invisible characters leaves an AI text watermark intact. Anthropic, Google and, since October 2026, OpenAI watermark generated text by biasing which word the model picks across a passage, so the signal is statistical and lives in the words. Stripping characters keeps every word in place. Only replacing the words themselves, thoroughly enough that the choices are no longer the model’s, reaches that kind of mark.

Which invisible characters do prompt injection attacks use?

Prompt injection and phishing attacks use the Unicode tag block, U+E0000 to U+E007F, which can spell out a whole sentence of ASCII invisibly, and the bidirectional overrides U+202A to U+202E, which reorder what a reader sees. Trojan Source demonstrated the overrides in source code and earned two CVEs. Neither group overlaps with the eight characters a zero-width scanner reports.

Why does a no-break space break find and replace?

A no-break space, code point 00A0, looks identical to an ordinary space but is a different character, so a search typed with the space bar will not match it. Word processors add it on purpose: LibreOffice inserts one before a colon or a question mark in French text. Folding 00A0 to an ordinary space before matching, as NFKC normalisation does, fixes the search.

Sources

  1. The Unicode Standard, Version 17.0.0 — Core SpecificationThe Unicode Consortium ·
  2. Unicode Character Database 17.0.0The Unicode Consortium ·
  3. HTML Standard — named character referencesWHATWG ·
  4. AutoCorrect — Localized OptionsThe Document Foundation ·
  5. bert/tokenization.py — text cleaning before segmentationGoogle Research ·
  6. Subsume JSON: string literals can contain U+2028 and U+2029V8, Google ·
  7. Trojan Source: Invisible Vulnerabilities32nd USENIX Security Symposium ·
  8. ASCII smuggling crosses over from AI prompt injection to phishing evasionMicrosoft Security ·
  9. How Claude’s text watermarking worksAnthropic ·
  10. New ChatGPT models seem to leave watermarks on textRumi ·
  11. Our approach to EU text provenance rulesOpenAI ·

In the AI watermark database

Remove the watermark from your draft. A watermark lives in the words a model picks. Anthropic says light editing probably won’t remove Claude’s watermark completely, while “a complete rewrite where every word is replaced will.” GenPolish’s Strong setting rebuilds every sentence, replaces as many words as it can and shows the share it replaced.Remove the watermark