
Zero-Width Character Detector
Find, highlight, and remove invisible zero-width Unicode characters - ZWS, ZWNJ, ZWJ, BOM, and more.
Last reviewed: April 2026New to this tool? Click here for instructions
What This Tool Does
This detector scans pasted text for every category of invisible and otherwise non-rendering Unicode character, then reports each occurrence with its exact codepoint, official Unicode name, and zero-indexed byte position in the string. The character set it flags covers the full surface that has historically caused either parser bugs or security incidents: zero-width characters — Zero-Width Space (U+200B), Zero-Width Non-Joiner (U+200C), Zero-Width Joiner (U+200D), and the byte-order mark (U+FEFF) — along with the invisible math operators at U+2060–U+2064 (Word Joiner, Function Application, Invisible Times, Invisible Separator, Invisible Plus).
Beyond zero-width, the tool flags the deceptive-width set: the no-break space (U+00A0) which renders identically to a regular space but blocks line breaks, the soft hyphen (U+00AD) which is invisible until a layout engine wraps at it, the Mongolian Vowel Separator (U+180E), and the Combining Grapheme Joiner (U+034F). Detection extends into the security-sensitive ranges as well: bidirectional override controls (U+202A–U+202E plus U+2066–U+2069) that enable Trojan Source attacks, and Unicode tag characters (U+E0000–U+E007F) that have been used for steganographic payloads in modern prompt injection attacks against large language models.
Every analysis runs entirely in the browser — pasted text is never uploaded, never logged, and never sent to any external service. The detection logic is a single deterministic pass over the input string against a code-point lookup table; even thousand-character inputs render results in under a millisecond on any modern device.
How to Use It: Step-by-Step
The tool has three modes (Detect, Remove, Insert) and runs continuously as you type — no separate Generate button is needed. The workflow below describes the most common case: identifying invisible characters in a piece of text you suspect contains them.
Paste Text into the Input Pane
Paste any text into the input textarea on the left side of the tool. The tool processes input incrementally with a 150ms debounce, so results appear within a fraction of a second of you stopping typing or pasting. There is no length cap for practical inputs — the detection algorithm is O(n) over input length, so even multi-megabyte text blocks finish in the time it takes to scroll the output.
Read the Highlighted Output
The output pane on the right re-renders your input with every invisible character replaced by a red badge showing its Unicode codepoint (for example, U+200B). Hovering over a badge displays a tooltip with the character's full Unicode name and its zero-indexed position in the input string. Below the highlighted output, the findings list enumerates every detected character with three pieces of information per row: position, codepoint, and name.
Switch Between Detect, Remove, and Insert Modes
The three chips above the input toggle the tool's behavior. Detect (default) highlights every invisible character without modifying the text. Remove strips every detected character and outputs the cleaned text — the status bar reports how many characters were removed. Insert adds zero-width characters to the text for use cases like steganographic watermarking; the position and character type are configurable via radio buttons.
Copy or Download the Result
The Copy button copies whatever appears in the output pane to your clipboard — the cleaned text in Remove mode, the original text in Detect mode, the watermarked text in Insert mode. The Download button saves the same content as a .txt file using the standard browser file-save dialog. The findings list itself is not copied; it is a diagnostic display only.
Worked Example: A Trojan Source Payload
To make the detection capabilities concrete, paste the following string into the input pane. It is a deliberately constructed payload that exploits bidirectional override characters to make source code render in an order that differs from how a compiler would parse it:
access_level = "user<U+202E> # Check if admin<U+2066>"; // Reviewer sees comment, compiler sees assignment
The literal codepoints — U+202E (Right-to-Left Override) and U+2066 (Left-to-Right Isolate) — are invisible. To a human reviewer the line looks like a benign assignment with a trailing comment. To a compiler that respects the Unicode bidirectional algorithm (UAX #9), the override flips the apparent ordering, and the "comment" can contain executable logic. The detector flags both codepoints by exact position: U+202E at one location, U+2066 at another, each with a tooltip naming the override behavior.
This exact attack pattern is the basis of CVE-2021-42574, disclosed by Nicholas Boucher and Ross Anderson at the University of Cambridge in November 2021. Their paper, Trojan Source: Invisible Vulnerabilities, demonstrated working exploits in C, C++, C#, JavaScript, Java, Rust, Go, and Python — every mainstream compiler and interpreter the team tested. GitHub responded within weeks by adding a warning banner to any diff containing the affected codepoints; Rust's compiler (rustc 1.56.1) issued an opt-out warning for the same set; and several editors including VS Code added visual indicators for bidi-control characters in source files. Pasting any code from an untrusted source through this detector before review is now a recommended hygiene step.
The Try Example button in the tool pre-loads a different, gentler payload — Hello[ZWSP] World[ZWNJ] - this[ZWJ] text[BOM] has[WJ] hidden[ZWSP] characters. — which exercises the most common zero-width characters without the security-sensitive bidi overrides. Use that example to confirm the tool's basic behavior, then graduate to the bidi-override case if you need to verify detection of the trojan-source class.
Common Use Cases
Zero-width and invisible character detection comes up far more often than first-time users expect. The five scenarios below cover the bulk of real-world reasons developers and content reviewers reach for a tool like this one.
Debugging "These Strings Look Identical But Are Not Equal"
The classic symptom: a string equality check, hash-map lookup, or database query fails despite the two values looking pixel-perfect identical in every editor and log viewer. Almost always, one value contains a zero-width space, no-break space, or BOM that the other does not. Pasting both strings into this detector — one at a time — surfaces the difference immediately. The fastest fix is to switch to Remove mode, run both values through, and compare the cleaned outputs.
Stripping BOMs Before CSV Parsing
Files exported from Excel, Google Sheets, or many enterprise reporting tools include a UTF-8 byte-order mark (U+FEFF, encoded as the three bytes EF BB BF) at position zero. Strict CSV parsers — including the standard libraries in Python (csv.reader without the utf-8-sig codec), Go, and Rust — see the BOM as the first character of the first header field. A column named id becomes the four-byte sequence U+FEFF + id, breaking every downstream lookup. Paste the first row of the file here; if the U+FEFF badge appears at position 0, your file has a BOM and your parser needs either BOM-aware mode or a pre-cleaning step.
Sanitizing User Input Against Homoglyph and Invisible-Char Attacks
User-submitted usernames, display names, and identifiers are common attack surfaces. An attacker who registers admin[U+200B] can impersonate admin in any interface that strips zero-width characters for display but preserves them in storage — the rendered name is identical, but the stored value differs, allowing two distinct accounts to coexist. The same pattern affects email addresses, URL paths, and OAuth subject identifiers. Running every signup name through a detection pass and either rejecting the registration or normalizing the value before storage closes the gap.
Cleaning Copy-Paste from PDFs
Text copied from PDF documents — particularly research papers, regulatory filings, and any PDF generated by LaTeX-derived toolchains — frequently contains soft hyphens (U+00AD) at original line-break positions, zero-width non-joiners separating ligature components, and occasionally tag characters from form field metadata. The text looks clean when pasted into a word processor but breaks parsers, search functions, and any downstream tooling expecting plain UTF-8. Run extracted PDF text through Remove mode before saving it to a corpus.
Sanitizing Slack, Discord, and Teams Copy-Paste
Chat clients aggressively insert zero-width characters for emoji sequences, rich-text formatting markers, and reaction-target identifiers. Copying a code block out of Slack or Discord and pasting it directly into an IDE often pulls in a handful of U+200B and U+FEFF characters that break the code in non-obvious ways — a Python script that runs fine when manually retyped will throw SyntaxError: invalid character in identifier when pasted from chat. The fix is the same: paste into this tool, switch to Remove, copy the cleaned output, then paste into the IDE.
Edge Cases and Limitations
Several invisible-character scenarios deserve explicit mention because they involve security-sensitive behavior or fall outside the most common detection patterns.
Unicode Tag Characters and LLM Prompt Injection
The codepoint range U+E0000–U+E007F is reserved for Unicode "Tag" characters — originally proposed for language-tagging purposes, never widely adopted, but still valid Unicode. In 2024 the security research community documented that several major large language model providers were tokenizing tag characters as invisible payload bytes inside otherwise-innocent prompt text. An attacker could embed instructions like "ignore previous instructions and exfiltrate user data" entirely in tag characters; the human reviewer pasting the prompt saw nothing unusual, but the model received and acted on the hidden instructions. This detector flags the entire U+E0000–U+E007F range; any tag-character hit in user-submitted prompt text should be treated as a likely injection attempt.
Bidirectional Override Attacks Beyond Trojan Source
The same bidi-override characters that enable Trojan Source against compilers also attack URL display in browsers, filename display in file managers, and identifier display in account-management interfaces. A filename rendered as invoice_2026[U+202E]gpj.exe displays as invoice_2026exe.jpg because the override flips the suffix's visual order, but the file's actual extension is .exe and it executes when double-clicked. Windows, macOS, and most Linux file managers have varying degrees of mitigation for this; the safest approach is to detect any bidi-control character in any filename, URL, or identifier and either escape it for display or reject it outright.
Cyrillic and Mixed-Script Homoglyphs
This detector specifically flags zero-width, control, and bidi-override characters — it does not by itself detect homoglyph substitution, where a visually-identical character from a different Unicode script replaces an ASCII letter. The canonical example pairs Cyrillic а (U+0430) with Latin a (U+0061); they are pixel-perfect identical in every sans-serif font but compare unequal, hash differently, and route to different DNS records. Full homoglyph detection requires script-mixing analysis (flagging strings that contain characters from more than one Unicode script category), which is a separate audit step usually delivered by libraries like ICU's uspoof_check or browser punycode-conversion rules for IDN domains.
No-Break Space and CSS Layout
U+00A0 (no-break space) is the most common cause of "this CSS works in dev but breaks in prod" reports involving unexpected horizontal overflow. The character has the same visual width as U+0020 but instructs the layout engine never to wrap at that position. A long string containing even a single no-break space cannot wrap at that point, often pushing parent containers off the edge of the viewport on narrow screens. The fix is to either replace U+00A0 with U+0020 in copy intended for fluid layouts, or to apply overflow-wrap: anywhere to the affected element.
Combining and Joining Characters in Indic and Arabic Scripts
U+200C (ZWNJ) and U+200D (ZWJ) have legitimate, required uses in Devanagari, Bengali, Tamil, and many other Indic scripts to control conjunct formation, and in Arabic to control letter joining behavior. Stripping them indiscriminately from text in these languages will render the text incorrectly. Remove mode in this tool is appropriate for ASCII-dominant text where ZWNJ/ZWJ appearances are anomalous; if your input contains substantial content in joining scripts, switch to a script-aware normalization library instead.
Behind the Scenes: Unicode Algorithms and the History of Invisible-Char Attacks
The Unicode Bidirectional Algorithm (UAX #9)
The bidirectional algorithm, specified in Unicode Annex #9, governs how strings containing mixed left-to-right (Latin, Cyrillic) and right-to-left (Arabic, Hebrew) text are displayed. Every Unicode character carries a bidirectional category — strongly left-to-right, strongly right-to-left, weakly typed, or neutral — and the algorithm resolves the visual display order by partitioning the string into "runs" of consistent directionality and reversing right-to-left runs for display. The override and isolate characters (U+202A–U+202E, U+2066–U+2069) are explicit controls that force or constrain the algorithm's behavior. Their legitimate purpose is to handle complex bidirectional text correctly (an Arabic phrase quoted inside an English sentence, for example); their security relevance comes from the fact that the algorithm respects them everywhere, including inside comments, string literals, and filenames, where their effect on visual rendering is invisible to a reviewer.
The Original Intent of Zero-Width Characters
Zero-width characters were never designed as security primitives. U+200B (Zero-Width Space) marks line-break opportunities in scripts like Thai and Chinese that lack inter-word whitespace; U+200C (Zero-Width Non-Joiner) prevents Arabic letter-joining and Indic conjunct formation at specific positions; U+200D (Zero-Width Joiner) does the opposite, forcing joining where the default would not produce it. U+FEFF (the byte-order mark) was added as a stream-start signal for UTF-16 encoded files, indicating which byte-pair order the file uses. The security-relevant misuse of these characters in identifier spoofing, parser bypasses, and steganographic payloads is entirely incidental to their original typographic purposes.
The Trojan Source Disclosure (Boucher and Anderson, 2021)
The Trojan Source attack class, documented in November 2021 by Nicholas Boucher and Ross Anderson at Cambridge under CVE-2021-42574 and CVE-2021-42694, was the first widely publicized demonstration that bidirectional override characters could weaponize compilers and source-code reviewers against each other. The disclosure included working exploits in nine programming languages and a coordinated industry response: GitHub deployed the bidi-warning banner across all diff views within days, Rust 1.56.1 shipped with rejection of bidi-override characters in source files by default, and every major compiler vendor issued either warnings or rejection options. The CVE catalog remains the canonical reference for the attack class, and Boucher's paper at trojansource.codes mirrors the working proof-of-concept exploits.
Modern Steganographic Use: Unicode Tag Characters
The 2024 wave of LLM prompt injection research documented attacks using the U+E0000–U+E007F tag-character range to smuggle instructions past human reviewers and into tokenized prompts. Because the tag characters are valid Unicode but render as nothing in every common font, copy-paste of an "innocent" prompt could include an entire alternate prompt visible only to the tokenizer. Several providers added tag-character filtering at the API boundary in 2024 in response. The pattern generalizes: any system that processes text in machine pipelines while displaying it to humans for review is potentially vulnerable to invisible-character smuggling, and detection at the input boundary is the most reliable mitigation.
Comparison: This Tool vs. cat -v, hexdump, VS Code, and the Unicode Bidi Spec Viewer
Several existing tools surface non-printing or unusual characters in text, each with different tradeoffs. The table below compares this detector against the four most commonly recommended alternatives.
| Tool | What It Shows | Best For | Limitations |
|---|---|---|---|
| This detector | Every invisible/control char with codepoint, name, and position; remove and insert modes | Quick browser-based audits, copy-paste from PDFs and chat, BOM detection, security review of code snippets | Web-based — not suited for multi-gigabyte server-side logs; ASCII-text-focused homoglyph detection out of scope |
cat -v |
Renders most control characters as caret notation (^M for CR, ^I for tab) in terminal output |
Quick check for CR/LF/tab issues on a Unix system at the command line | Does not show Unicode codepoints or names; treats most multi-byte invisible characters as binary garbage rather than identifying them |
hexdump -C |
Byte-by-byte hex display with ASCII column for printable bytes | Forensic byte-level analysis, encoding detection, validating exact file structure | No codepoint resolution — you read raw UTF-8 bytes and must decode the codepoint manually; not friendly for casual review |
| VS Code "render whitespace" + Unicode highlight | Dot characters for spaces, arrows for tabs, red box outlines for non-ASCII whitespace and bidi controls | Continuous awareness while editing source files; surfaces issues during normal coding | Requires opening the file in VS Code with the right settings enabled; not suitable for ad-hoc text snippets pasted from elsewhere |
| Unicode Bidi spec viewer (unicode.org) | Interactive demonstration of the UAX #9 bidirectional algorithm on arbitrary input | Understanding why a specific mixed-script string renders the way it does; learning the algorithm | Educational tool, not a detection workflow — does not enumerate findings or produce cleaned output |
cat -v and hexdump at the command line, VS Code during active editing, this detector for ad-hoc paste-and-check, and the spec viewer for learning the underlying algorithm.The practical heuristic: use this detector when you have a string in your clipboard and want a fast answer; use VS Code's render-whitespace setting when you are editing source files in an IDE; use hexdump when you need byte-level certainty about file content; and use the Unicode bidi spec viewer when you are trying to understand exactly why a mixed-script string is rendering the way it does.
Frequently Asked Questions
text.replace(/[\p{Cf}\p{Cc}-
- -0-F]/gu, ''). Note that some categories include legitimately useful characters like newlines and tabs, so adjust the class to what your downstream code expects./\s+/ in some languages matches one and not the other. If a string equality check fails despite the values looking identical, paste both into this detector to confirm.