Ghost Ink

Hide a secret. Reveal the trick.

Can you hide a message in plain sight? In an emoji? Inside a picture? Try it, reveal the trick, and discover why different kinds of secrets need different ways of finding them.

This sentence carries a hidden message. Nothing looks wrong.

Take a three-stop ghost tour

Start anywhere, or follow all three stops. Each experiment gets its own little guide. Prefer to explore? All the toys are below.

  1. 1 · Reveal the ghost

    Use Reveal hidden characters in the sentence above. The visible sentence stays familiar, but extra characters carry a message.

    Try the hidden sentence

    Notice: looking at the sentence and inspecting its characters give different answers.

  2. 2 · Meet the disappearing words

    In What a scraper reads, choose White on white, then compare the rendered page with the extracted text.

    Try the disappearing words

    Notice: ordinary text can be hidden by rendering rules. A character scan alone cannot explain what the viewer sees.

  3. 3 · Haunt a picture

    Choose Hide it in the pixels, inspect the amplified difference, then choose Read it back.

    Hide a secret in a picture

    Notice: the message lives in pixels, so a text inspector has no payload to examine.

Ready to apply the idea? Try the detector challenge, or explore any layer below.

Hide a message

Your secret becomes invisible tag characters tucked into ordinary text.
Carrier
Output appears here — it will look identical to the cover text.

Find a message

Paste text that may carry Ghost Ink. The hidden layer is pulled back out.
Recovered message appears here.

Inspect & clean

The defensive side. X-ray any text for hidden or deceptive characters — not just Ghost Ink’s Tags block, but the whole family attackers use — then strip them out. This is how a filter, or a careful reader, defeats the trick.
Load an example:
Nothing inspected yet.

Play with it

Four ways to feel the technique instead of reading about it: a guessing game you cannot win by looking, the model’s side of a prompt injection, a survivability test you run through your own apps, and a leak tracer.

Spot the Ghost

Some of these messages carry a hidden payload. Mark the ones you think are haunted, then check. You will not be able to tell by looking — that is the whole lesson.
Mark your guesses, then check.

What the model reads

The attack that started all of this. An instruction is mapped straight into the Tags block — no container, no base64 — so a language model reads it as ordinary text while the human reviewing the same string sees nothing.

Does it survive the trip?

The honest limit, made testable. Copy the probe, send it through Slack, Gmail, Notes, your CMS — anywhere — then copy it back out and paste it below. The probe carries a known count of every category, so the page can report exactly what made it through.
The survival report appears here.

Hide it behind one emoji

Variation selectors attach to whatever character comes before them, so an entire payload can ride on a single emoji. What you paste is one glyph; what travels is the whole message. The emoji offered here are all single codepoints on purpose — one like ❤️ already ends in a variation selector of its own, which would corrupt the payload.
Host
Your loaded emoji appears here.

Look-alike forge

A different family entirely: not hidden characters but visible impostors. Latin letters are swapped for characters from other scripts that render almost identically. Nothing is concealed here — the string simply lies about what it is. This is the homoglyph lineage behind IDN phishing, and the reason browsers show some domains in Punycode. The table behind this panel is a working subset inspired by UTS #39, not an implementation of it — see KNOWN-GAPS.
The look-alike appears here.

Leak tracer

One document, several recipients, a different invisible watermark in each copy. Paste a leaked copy back and the page names who it came from. This is a real deployed use of the technique, not a hypothetical.
Stamped copies appear here, one per recipient.
The tracer names a recipient here.

The same trick, other disguises

Everything above hides data in characters. These five hide it somewhere else entirely — in the order characters display, in what a renderer chooses not to show, in what your clipboard receives, in what a string becomes after it is normalised, and in the low bit of an image. Same shape of failure every time: two readers of one artefact disagree, and only one of them is you.

Display order vs. stored order

Bidi control characters change the order text is drawn without touching the order it is stored. A reviewer reads the drawing; a compiler, a filesystem and a filter all read the storage. Boucher and Anderson named this Trojan Source in 2021 (CVE-2021-42574).
Load

What a scraper reads

Back to the ghost tour · Detector challenge

No unusual codepoint is involved here at all. The text is perfectly ordinary — the renderer is told not to draw it. A model reading the page through textContent, or a scraper reading the DOM, gets every word of it. This is how prompt injection actually arrives on the open web.
Load

What actually reaches your clipboard

A page can intercept the copy event and put something else on your clipboard. The block below shows one command; copying it gives you another. The replacement here is deliberately inert and announces itself — the mechanism is the point, and this is why you paste into an editor before you paste into a shell.
echo "Boo! Just passing through."
Copy it, then look at what landed.

Clean before, dirty after

A filter that checks a string before the system normalises it is checking something that will not exist by the time it matters. Unicode normalisation and case folding both turn innocent-looking characters into the exact ones you were blocking.
Load
The blocklist verdicts appear here.

There’s a ghost in this picture

Back to the ghost tour · Detector challenge

A different medium, the same idea. Each pixel's red, green and blue values get their least significant bit replaced with one bit of your message — a change of at most 1/255 per channel, which no eye resolves. The difference view amplifies it 64× so you can see where the payload actually sits.
Original
Carrying the message
Whole-image difference · brightness 64×
Ghost footprints · first 32 × 8 pixels, enlarged 10× · brightness 64×

Hide a message to reveal its pixel footprints. Black means unchanged; coloured squares show changed channels. The zoom shows the top-left corner, where the message starts.

Hide something, then read it back out of the pixels.

Can you catch the trick?

Six short scenarios. Choose the inspection suited to each example, then read why. These are teaching cases, not guarantees about every detector implementation.

In the news: invisible watermarks and SynthID

7 October 2026 · Related technology

Google announced global access in English to its SynthID Detector for images, video, and audio, including watermarks from participating providers. It offers a real-world example of the lesson behind the image-pixel demonstration: an invisible signal needs a detector designed for that signal.

Ghost Ink carries a message in image low bits; SynthID embeds watermarks to help identify AI-generated or edited media. Ghost Ink’s Unicode inspector and image decoder do not detect, remove, or test SynthID watermarks.

No watermark detected does not mean human-made. A detector checks for signals it supports. An absent signal cannot establish who created the content or whether its claims are true.

What about text? SynthID’s separate text-watermarking method adjusts token probabilities during generation. It does not depend on inserting invisible Unicode characters. The October announcement concerns the public detector for images, video, and audio.

Primary sources: Google’s announcement · How SynthID works. Reporting: Ars Technica. These links open external sites; the demonstration runs locally.

Which tool spots which trick?

Compare all the tricks

Compare the inspection each trick needs. The Inspect & clean panel can expose unusual characters; tricks in CSS, clipboard transfers, and image pixels need other checks.

Survivability is a general expectation, not a measurement. The survivability lab is there so you can measure the paths you actually care about instead of trusting this column.

Why does this work?

Peek under the hood

Unicode reserves a block called Tags from U+E0000 to U+E007F. Almost all of it mirrors plain ASCII: U+E0041 stands in for “A”, U+E0061 for “a”, U+E0020 for a space. The characters exist in the text and travel with it, but fonts render them to nothing, so a person sees only the cover text.

  1. Your secret is turned into bytes, then Base64 so every character lands in the printable ASCII range.
  2. Each of those characters is shifted up by 0xE0000 into the Tags block — same letter, now invisible.
  3. The invisible run is appended to (or scattered through) the cover text. Position doesn’t matter: a reader collects the tag characters in order and shifts them back down.
Container format. The current v2 record begins with GHST, version, mode, KDF parameters, and declared lengths, followed by salt, nonce, and payload as applicable. Plaintext carries a CRC-32 corruption check; it provides no authentication. With encryption on, PBKDF2-SHA256 derives the key (210,000 iterations by default), and AES-256-GCM authenticates the header, salt, nonce, and message. Everything runs in your browser. Read the v2 specification.

Do people really use these tricks?

Read the real-world story

This is not a party trick. The exact carrier this page demonstrates — the Unicode Tags block — is a live technique known as ASCII smuggling. It has had two lives, and a user’s suspicions are never raised in either one.

Life one · against AI

Smuggling instructions into a model

Malicious instructions are written in invisible tags and pasted into an email, a web page, or a document an LLM will process. The model reads the hidden commands as ordinary text; the human reviewing the same content sees nothing. This is a stealthy form of prompt injection, and it’s where the technique first drew attention.

Life two · against filters

Obfuscating keywords from a detector

Spammers slip invisible tags inside trigger words so a filter no longer recognizes them, while the recipient reads the word normally. Same mechanism, inverted goal: instead of adding a hidden instruction, they’re hiding a visible keyword from the machine that scores it.

In early 2026, Microsoft watched the second use explode. Hits on its ASCII-smuggling hunting signature in Microsoft Defender for Office 365 rose from roughly 21,000 on Sunday 8 February 2026 to more than 1.3 million the next day, and to more than 2.3 million on 11 February. The campaign then held a strict weekly cadence — heavy on weekdays, collapsing to near zero on Sundays — and reached its peak of 2.37 million messages on 26 February 2026. Elevated volumes persisted for about three months before dropping sharply after 15 May. The bulk of it was finance-themed spam: fake pre-approvals for credit lines and “bridge funding.”

One figure from the same research is worth more than the spike: Microsoft reports that over 99% of these messages were caught anyway — by sender, IP, URL and domain reputation, ML classification, brand-impersonation checks and authentication, not by noticing the invisible characters. The Unicode trick defeated one layer, and the other layers held. That is the shape of a working defence, and it is the argument this whole page is making.

~21K
signature hits per day, pre-onset baseline
2.37M
peak daily messages, Feb 26, 2026
>99%
caught by other layers anyway
3 mo.
elevated Feb 9 → May 15, 2026
Only the figures Microsoft published are plotted. Weekdays between these dates ran between roughly 1 million and the 2.37 million peak, but were not published as daily values, so no line is drawn through them. Source: Microsoft Security Blog, 3 September 2026.

The email angle is subtler than plain hiding, and it is aimed squarely at machine-learning classifiers. A word like funding is a clean, familiar unit to a model’s tokenizer. Slip an invisible U+E0020 into the middle and the tokenizer may split it into fun and ding, or emit rare tokens the model has never weighted — while the recipient still reads “funding.” The lure survives; the filter’s view of it does not. Want to see it? Load the spam lure in the Inspect & clean panel above and X-ray it.

The property that makes tag characters useful for smuggling instructions into a model is the same property that makes them useful for hiding a keyword from a filter: they are invisible to a person and present to everything that processes text. The intent is inverted; the mechanism is not. — the mechanism in summary. Not a quotation; see REFERENCES for the sources.

Camouflaging trigger words isn’t new — spammers have leaned on zero-width and non-breaking spaces for decades to break literal-string matches. What’s new is the target. The bigger prize is no longer defeating a keyword search; it’s defeating the ML- and NLP-based models that now drive spam and phishing classification. Unless a filter renders the message to an image and runs OCR over it, the invisible tags can quietly change what the model “reads.”

All telemetry figures are taken from the primary source: “ASCII smuggling crosses over from AI prompt injection to phishing evasion,” Microsoft Security Blog, 3 September 2026. Secondary reporting: Dan Goodin, “Once popular for attacking AI, ASCII smuggling is embraced by spammers,” Ars Technica, 4 September 2026. This page is an independent educational demo and is not affiliated with either.

How do you spot the trick?

Explore defences and limitations

The Inspect panel above is a friendly version of checks that already ship in tools you probably use every day. Trojan Source is why most of them exist.

  • VS Code — Unicode highlighting. The editor flags invisible, ambiguous and non-basic-ASCII characters inline. It is on by default for ambiguous characters; the settings that govern it live under editor.unicodeHighlight.* (invisibleCharacters, ambiguousCharacters, nonBasicASCII). The closest productised sibling to this page’s detector.
  • GitHub — bidirectional-text warning. Diffs and blobs containing bidi control characters get a banner, added directly in response to Trojan Source.
  • The Rust compiler. A lint fires on bidi control codepoints inside string literals; other toolchains added equivalents. Boucher and Anderson’s 2021 Trojan Source paper (CVE-2021-42574 / 42694) is the common ancestor of all of these.
  • Your browser, on every address bar. The Punycode display rules and Unicode’s UTS #39 confusables data are the defence against the look-alike forge above — which is why that panel shows you the xn-- form the browser would fall back to.
  • Microsoft Defender for Office. The ASCII-smuggling signature behind the chart further up this page.

These defences act at different layers: editors expose suspicious characters, compilers diagnose source, browsers choose how to display addresses, and mail systems classify messages. The Inspect panel illustrates one part of that defence: making hidden characters visible for review.

What this page deliberately does not do

This exhibit includes character, rendering, interaction, transformation, and image-pixel demonstrations. The comparison table records which inspections catch each one. These neighbouring techniques remain outside its implemented scope:

  • Stego in a model’s token choices. Meteor (Kaptchuk et al.) and arithmetic-coding schemes hide bits in which word a language model picks, so the output is ordinary text with no anomalous codepoint to find. These techniques require their own generation and decoding machinery; this exhibit does not implement them.
  • Watermarking generated text. SynthID-Text and green-list schemes (Kirchenbauer et al.) run the idea backwards: instead of smuggling something into text, they bias generation so the output carries a statistical signature. The Leak tracer above uses a different mechanism: explicit character-based recipient tags. See the SynthID note for the distinction.
  • Other media techniques. Robust media watermarking and glyph-shape perturbation in fonts are outside this exhibit. The image panel demonstrates basic least-significant-bit steganography only.

Where this is weak

  • Hiding the content is strong; hiding its existence is not. The AES-GCM layer protects what the message says even if the text is intercepted. But the presence of a payload is trivial to detect — the Inspect panel above does it in one pass, and so do published filter signatures.
  • It does not survive everywhere. Many platforms and editors normalize or strip tag characters on paste or send, so the ghost layer can silently vanish in transit.
  • Not a substitute for a covert channel. Treat this as a teaching model of a known technique, not a way to move messages past a real adversary. Modern detectors already flag it — which is exactly why understanding it matters for defense.

Real primitives, no simulated math. Crypto via WebCrypto SubtleCrypto. Everything runs client-side.