docs
stormdoor.dev
FINDING

prompt.hidden_text

The text contains zero-width, bidi-control, Unicode Tag, variation-selector, filler or private-use characters that hide content from a human reader.

SEVERITY
high
LEADS TO
block
GATE
/v1/prompts
CHECK
prompt.hidden_text
Jump to what to do

Source: rules.

What it looks at#

Invisible or near-invisible Unicode, in these categories (data.matches[].rule):

  • zero_width — zero-width space/joiner/non-joiner, word joiner, BOM, Mongolian vowel separator.
  • bidi_control — left-to-right/right-to-left/Arabic letter marks and embedding/override/isolate controls.
  • format — soft hyphen and the invisible math operators.
  • unicode_tag — characters in the Unicode Tag block (U+E0000–U+E007F), the documented “ASCII smuggling” channel: these render as nothing in virtually every font, but many tokenizers round-trip them as if they carried the shadowed ASCII text, making them a way to hide an entire instruction from anyone reading the rendered text.
  • variation_selector — a run of two or more variation selectors.
  • filler — Hangul fillers (U+115F, U+1160, U+3164, U+FFA0) outside Hangul text and the Braille blank (U+2800).
  • braille — a run of Braille pattern characters that decodes to readable text.
  • private_use and unassigned — runs of two or more private-use or unassigned code points. A single private-use character (the Apple logo, an icon-font glyph) is ignored.

Ordinary whitespace is never flagged. These are deliberately excluded because they are invisible but not evasive: a zero-width joiner inside a real emoji sequence (👨‍👩‍👧‍👦) or between Arabic/Indic letters, a single variation selector after an emoji, left-to-right/right-to-left marks next to right-to-left script, a BOM at the very start or end of the text, Hangul fillers inside Hangul text, and the tag sequence in subdivision-flag emoji.

When hidden characters decode to readable text the run is reported as prompt.unicode_smuggling instead — one finding per run.

Why it matters#

Every category here exists to make text render differently, or render nothing at all, compared to what a model actually processes. /v1/spam’s equivalent check treats this as evasion for splitting a blocked word; here the risk is bigger — hiding an entire instruction inside text that looks empty or ordinary to a human reviewer.

What to do#

High severity — on its own this pushes the verdict to block. There is essentially no legitimate reason for ordinary user-facing text to contain Tag-block characters at all; zero-width/bidi characters have rare legitimate uses (Mongolian text, genuinely bidirectional content), so check data.matches[].rule before assuming intent.

OTHER FINDING CODES FROM /V1/PROMPTS