Guide — text

The characters you can't see

Invisible characters are legitimate parts of Unicode that land in your text mostly by accident. What each one is for, what actually breaks, what the watermark evidence really says, and how to clean text without wrecking your emoji.

Written by the maker of these tools Updated 2026-08-18 9 min read

The short version: invisible characters are legitimate parts of Unicode that most text never needs, and they end up in yours mostly by accident: copy-paste from the web, Word autocorrect, a PDF export. They can break code, forms and searches; they can also carry hidden data, which is real but rarer than the panic suggests. This page explains what each character is for, what actually breaks, what the 2026 evidence says about AI watermarking, and how to clean text without wrecking your emoji or someone else's language.

Why does Unicode even have invisible characters?

Because writing systems need them. Before treating any of these as malware, know what they do when they are doing their job.

Zero-width joiner (U+200D) is the glue inside modern emoji. The family emoji is three separate emoji joined by two invisible ZWJs; delete them and the family falls apart into three people. The same character requests joined letterforms in Indic scripts and Arabic-script typography.

Zero-width non-joiner (U+200C) does the opposite: it keeps letters from connecting. Persian depends on it. Everyday verb forms like "miravam" (I go) are spelled with a ZWNJ between the prefix and the stem, and stripping it produces text a Persian reader sees as misspelled. German and other languages occasionally use it to break unwanted ligatures.

Soft hyphen (U+00AD) marks where a word may break at a line end. Invisible until the line wraps, then it renders as a hyphen. Publishers and CMSs insert it deliberately for long words in narrow columns.

No-break space (U+00A0) keeps two words on the same line. French typography requires a no-break or narrow no-break space (U+202F) before « ! », « ? », « ; » and inside guillemets; word processors insert them automatically and correctly.

Bidirectional controls (U+200E, U+200F, U+202A–U+202E, U+2066–U+2069) exist so Hebrew and Arabic can coexist with left-to-right text in one paragraph. Mixed-direction text is impossible to lay out correctly without them.

Variation selector 16 (U+FE0F) tells a renderer "show the colorful emoji form." The red heart emoji is U+2764 plus VS16; without the selector many platforms render a plain black glyph.

None of this is an attack. The trouble starts when these characters leave the contexts they were designed for and land in your source code, your CSV, or your username field.

Where do they sneak into my text?

Almost always through copy-paste. Web pages use NBSP for layout, so copying a sentence often copies one. Word and Google Docs autocorrect straight quotes to curly ones and insert no-break spaces around punctuation. PDFs are the worst offenders: text extracted from a PDF routinely carries soft hyphens from line breaks and exotic spaces from justification. Editors and Windows tools sometimes prepend a byte order mark (U+FEFF) when saving UTF-8 files. Chat apps and social platforms pass zero-width characters through untouched, so anything upstream survives every hop downstream.

Why does my text break?

A few failure modes account for most of the pain:

Code with a fake space. U+00A0 looks identical to a space in most editors, but let x = 1 with a no-break space is a syntax error in many languages and a silent bug in others. This is the classic "the code is identical to the working version but fails" ticket.

JSON or config with a BOM. A U+FEFF at the start of a file is invisible in editors that honor it, but strict JSON parsers and some shells reject the file or misread the first key.

Search and matching failures. "café" containing a zero-width space between letters will not match "café". Deduplication, spreadsheet lookups, and database joins all quietly fail.

Spoofed identifiers. A username or handle padded with a zero-width character looks identical to the real one but is a distinct string, which is a phishing primitive on any platform that accepts raw Unicode in names. Browsers and domain registries restrict most of this in domain names, but application-level username fields often do not.

Trojan Source. The sharpest version of the problem: CVE-2021-42574, published in 2021 by Nicholas Boucher and Ross Anderson at Cambridge. Bidirectional override characters placed in source code make the code a human reviewer sees differ from the logic the compiler executes, and the technique applied to compilers and interpreters for C, C++, Java, JavaScript, Python, Rust, and Go. Compiler vendors and code hosts responded (GitHub now flags bidi controls), but the class of attack is why bidi characters outside genuinely bidirectional text deserve suspicion.

Reference table

Character Code point Legitimate use Risk when out of place
Zero-width space U+200B Line-break hint in long unspaced strings Breaks search, matching, identifiers; steganography carrier
Zero-width non-joiner U+200C Persian and other joining scripts; ligature control Spoofed identifiers; steganography carrier
Zero-width joiner U+200D Emoji sequences; Indic and Arabic shaping Spoofed identifiers; steganography carrier
Word joiner U+2060 Non-breaking glue without width Invisible string mismatch
BOM / zero-width no-break space U+FEFF UTF-8/UTF-16 byte order signature at file start Breaks JSON, shells, and parsers mid-text
Soft hyphen U+00AD Hyphenation hint in typography Corrupts extracted or pasted text invisibly
Bidi controls U+200E, U+200F, U+202A–202E, U+2066–2069 Mixed Hebrew/Arabic and Latin text Trojan Source; display-order spoofing
Variation selectors U+FE00–FE0F Emoji presentation (VS16); CJK glyph variants High-capacity steganography carrier
No-break space U+00A0 French punctuation; keeping units with numbers Fake space in code and data
Typographic spaces U+2000–200A Fine spacing in typesetting Invisible mismatch in pasted text
Narrow no-break space U+202F French punctuation; number grouping Same, plus the 2025 "AI watermark" scare below
Ideographic space U+3000 CJK full-width spacing Fake space in code and forms

Can hidden characters track or watermark me?

Yes, this is technically real and has been demonstrated for years. In 2017 Zach Aysan published the fingerprinting technique: encode a user ID as a pattern of zero-width characters and slip it into text, and the mark survives copy-paste and formatting removal. Public tools such as Steganographr encode arbitrary messages this way. A company could serve each employee a subtly different copy of a confidential memo and identify the leaker from a pasted excerpt.

The honest caveats: the payload is fragile (any character-level cleaning destroys it), there is no public evidence of routine, large-scale fingerprinting of ordinary web text, and detection is trivial once you look. If you handle sensitive text, running it through a cleaner before republishing is a reasonable habit, not paranoia.

Is AI-generated text watermarked with invisible characters?

Mostly no, and the cases that matter do not work at the character level. Here is the state of the evidence as of August 2026.

The April 2025 episode. Users found narrow no-break spaces (U+202F) and related characters in output from OpenAI's o3 and o4-mini models, and "hidden ChatGPT watermark" stories spread. The characters were real. Whether they were a watermark was never confirmed; within days, the researchers who found the pattern reported that OpenAI called it a quirk of large-scale reinforcement learning rather than a watermark, and that new outputs no longer contained the characters. Either way, a find-and-replace defeated it, which is exactly why serious watermarking does not work this way.

OpenAI. Reporting since 2024 says OpenAI has had a working text watermarking system internally for years and has chosen not to deploy it. The design, described publicly by Scott Aaronson in 2022, biases which tokens the model samples using a cryptographic function. That is a statistical signal spread across word choices, not inserted characters.

Google. SynthID-Text is deployed in Gemini and open-sourced. DeepMind's own description is explicit: it modulates token probabilities during generation, and the watermark is the resulting distribution of choices. There is no character to find and no character to strip.

Anthropic. On August 11, 2026, Anthropic announced that Claude models released after August 2, 2026 will watermark generated text, driven by the EU AI Act's Article 50 transparency rules that became enforceable that month. Anthropic says the mark travels with copied text and may not survive heavy editing, and uses C2PA signed metadata for files. As of this writing the text technique has not been publicly specified, so whether it involves characters, statistics, or both is unknown. Treat any confident claim about its mechanism as speculation.

The practical takeaway: invisible characters in AI output today are usually artifacts of training data, not tracking. The watermarks that are actually deployed are statistical, and no character cleaner can see or remove a statistical watermark. Anyone selling "AI watermark removal" via character stripping is selling a placebo against everything except pasted-in gremlins.

How do I see and remove invisible characters?

To see them: VS Code has highlighted invisible and ambiguous Unicode characters by default since version 1.63, and highlights all non-ASCII characters in untrusted workspaces; a hex dump (xxd, Format-Hex) shows everything; or paste into our Hidden Character Cleaner, which counts every hidden character by name the moment text lands, entirely on your device.

To remove them safely, the rules matter more than the regex:

  1. Never blind-strip zero-width characters. Deleting every U+200D destroys emoji: families, couples, the runner, most profession emoji are ZWJ sequences. Deleting U+FE0F downgrades the red heart to a plain glyph. Our cleaner handles this with a neighbor check: ZWJ and variation selectors are kept when they sit next to an emoji and removed everywhere else.
  2. Know your language context. If the text is Persian, or any script that needs joiners in ordinary prose, do not run it through a blanket stripper at all, ours included. The emoji exemption protects emoji, not prose joiners. Cleaning is for text that should not contain these characters: English and European copy, code, identifiers, data.
  3. Normalize exotic spaces to a plain space rather than deleting them. Deleting a no-break space merges two words; replacing it preserves the sentence. The cleaner converts NBSP, U+2000–200A, narrow no-break space, and ideographic space to U+0020.
  4. Strip BOMs from file starts and data, always. No JSON or script benefits from one.
  5. Reject bidi controls in code and identifiers outright. In prose that genuinely mixes directions, leave them alone.

Try it on your own text: paste anything suspicious into the Hidden Character Cleaner, see every hidden character named, and strip them with your emoji kept intact.

Open Hidden Character Cleaner

Fair questions

Are invisible characters dangerous by themselves?

No. They are display and layout instructions. The risk is contextual: fine in French prose or emoji, suspicious in source code, usernames, and JSON.

Will cleaning text remove an AI watermark?

It removes character-level artifacts like the April 2025 NNBSP pattern. It cannot touch statistical watermarks such as SynthID, which live in word choice, not characters.

Why does my code fail when it looks identical to working code?

Almost certainly U+00A0 posing as a space, or a BOM at the start of the file. Hex-dump the line and compare byte by byte.

Does the cleaner send my text anywhere?

No. It runs in your browser; the page makes no network requests with your text.

The working rules

Written at the forge. This guide is by Justas Jarmalavičius, the independent maker of plainforge's 43 browser-local tools and the 34-site network around them. Guides here exist only where the workshop has real standing: we describe mechanisms we have implemented ourselves, and every claim is checked before it ships.

Sources and further reading

  1. Krebs on Security: Trojan Source bug — CVE-2021-42574 and how bidi overrides hide malicious logic in reviewed code.
  2. Wikipedia: Trojan Source — the attack, affected languages, and vendor responses.
  3. Zach Aysan: Zero-width characters (2017) — the original zero-width fingerprinting technique.
  4. Steganographr — a public tool that hides messages in zero-width characters.
  5. WinBuzzer (April 2025) — the o3/o4-mini narrow no-break space episode and the watermark debate.
  6. WindowsForum analysis — the tokenization-artifact explanation of the same episode.
  7. Forbes (August 2026) — OpenAI's undeployed internal watermark and the wider industry picture.
  8. Google DeepMind: SynthID — how SynthID-Text modulates token probabilities at generation time.
  9. Google AI: SynthID documentation — the deployed statistical watermark, in Google's own words.
  10. TechCrunch (August 2026) — Anthropic's watermarking announcement and the EU AI Act Article 50 driver.
  11. Fortune (August 2026) — corroborating coverage of the Anthropic announcement.
  12. VS Code 1.63 release notes — invisible and ambiguous Unicode highlighting on by default.