Why Persian Chatbots Are More Vulnerable to Prompt Injection Attacks

Persian script's variants naturally bypass string filters; defense should normalize first, then filter, with controls outside the model.
The article explains that prompt injection's core problem is the model's inability to distinguish instructions from ordinary text, and that indirect injection via retrieved documents is more dangerous than direct injection. Persian's homoglyphs, zero-width characters, three numeral sets, Finglish, and directional control characters form five bypass forms invisible to string filters. The author recommends normalizing at the boundary first, then filtering, preserving original text for display and logs, and never stripping the required ZWNJ. Testing should be single-variable and repeated with success-rate reporting. Durable defense must live outside the model: least privilege, human approval, treating output as untrusted, graded trust for retrieved content, and monitoring.
Title
Why Persian Chatbots Are More Vulnerable to Prompt Injection Attacks
Meta Description
Why do prompt injection attacks on Persian chatbots bypass English filters? This article breaks down five bypass forms—homoglyphs, zero-width characters, multiple numeral sets, and Finglish—and presents a single-variable testing workflow plus a "normalize first, then filter" defense strategy.
Article Content
English filters are nearly useless against Persian prompt injection. The reason isn't that the attack techniques are novel, but that the Persian writing system itself provides multiple natural channels for bypassing string matching—the same word looks identical on screen but is a completely different character sequence in code.

Two Basic Facts About Prompt Injection
Language models read system instructions, user messages, retrieved documents, and tool outputs as a single stream of text. They have no reliable way to distinguish which part is an instruction and which part merely "looks like an instruction." This means any path that can feed text into the model's context is a potential attack surface.
More dangerous still is indirect injection: the attacker doesn't need to converse directly with the application—they only need to place malicious text in a document, ticket, or web page that the application will later read. The victim does nothing suspicious and the attack has already occurred. Most teams only guard user messages while assuming retrieved content, file names, third-party API responses, and error messages are safe—these are precisely the common entry points for indirect injection.
Why Persian Breaks Filters
Existing filters either do string matching or use small models for classification. Both assume text arrives in the form it had when the rules were written, and Persian breaks this assumption along multiple dimensions—each variant being semantically unchanged for the model.
Homoglyphs. The Persian "ی" and the Arabic "ي" look identical in most fonts but are different Unicode characters; the same applies to "ک" and "ك". A denylist written with one form cannot see the other. The following code illustrates this blind spot:
denylist = ["کیک"] # U+06A9 U+06CC U+06A9: Persian kaf, Persian yeh
text = "كيك" # U+0643 U+064A U+0643: Arabic kaf, Arabic yeh
text in denylist # False: looks identical on screen, different code points
normalise(text) in denylist # True: after mapping Arabic letters to Persian letters
Invisible spaces. The zero-width non-joiner is a legitimate component of Persian spelling—removing it degrades readability, so applications cannot simply strip it. But there are also other completely invisible zero-width characters that, when inserted mid-word, show the reader the same word while string matching sees a different one.
Three numeral sets. Persian digits, Arabic-Indic digits, and Latin digits all appear in Persian text. Rules written for one set will miss the other two.
Finglish. Writing Persian in Latin script is very common in Iran, and LLMs read it without difficulty. But for a filter armed with a Persian vocabulary list, this text isn't Persian at all.
Directional control characters. Unicode contains characters that change the display direction of text. With these, the content a support agent reads in the admin panel and the string the model actually receives can be entirely different. This class of attack is known as Trojan Source in the source code domain and is equally effective in natural language text.
Unicode Normalization Forms: Unicode defines four normalization forms (NFC, NFD, NFKC, NFKD) for mapping character sequences that are visually equivalent but have different code points to a unified representation. For example, accented characters can be represented either as a single code point or as a base character plus a combining mark; NFC merges them, while NFD does the opposite. But note: normalization forms primarily handle combining characters and compatibility characters and cannot solve the problem of Arabic-Persian homoglyphs—that requires an explicit character mapping table (e.g., mapping U+064A to U+06CC), which is precisely the "map Arabic characters to their Persian equivalents" step recommended in the original article. In a defense pipeline, you should first do homoglyph mapping, then apply NFC or NFKC for general normalization—the two are complementary, not substitutes.
Zero-Width Non-Joiner (ZWNJ): U+200C, used in Persian to prevent adjacent letters from joining, is part of the orthography. For example, the ZWNJ in "میروم" (I go) separates the prefix from the root; removing it changes the spelling's validity. In contrast, the zero-width space (U+200B), zero-width joiner (U+200D), etc. typically serve no orthographic function in Persian and can be safely removed. Filter design must distinguish these two categories: stripping all zero-width characters breaks Persian text, while stripping only non-functional zero-width characters preserves readability and closes the bypass channel.
Trojan Source: A class of source code supply chain attack disclosed in 2021 that uses Unicode bidirectional control characters (such as U+202E right-to-left override) to make source code appear in a different order in editors than how the compiler actually parses it. An attacker can hide malicious logic in a comment so that reviewers see harmless code while the compiled result contains a backdoor. The original article notes that this technique is equally effective in natural language text: text rendered in an admin panel may present semantics dramatically different from the raw string received by the model due to directional control characters, thereby bypassing human review.
Variant Forms Worth Testing
The original article provides a set of variant categories rather than ready-made attack payloads, with the goal of letting teams build their own test sets:
| Form | What it does to text | What it bypasses |
|---|---|---|
| Homoglyph substitution | Replaces "ی" and "ک" with Arabic twin characters | Wordlists, string rules |
| Zero-width character insertion | Breaks from within a word without changing appearance | Exact matching, some classifiers |
| Numeral set switching | Switches between Persian, Arabic-Indic, and Latin digits | Rules written for a single numeral set |
| Finglish | Writes the same sentence in Latin letters | Persian vocabulary filters |
| Mixed script | Mixes Latin look-alikes into Persian words | String matching, incomplete normalization |
| Directional control characters | Separates displayed content from received content | Human review, raw-rendered logs |
| Indirect injection | Places text in documents the application later reads | Any control covering only user messages |
When testing, change only one variable at a time—otherwise you can't tell which change made the difference.
A Reusable Testing Workflow
- List all text entry points. User messages are just one of them—uploaded file names, retrieved document bodies, ticket titles, third-party API responses, and error messages from other systems are all text. What isn't listed won't be tested.
- Establish a baseline. First send an instruction in plain, unmodified Persian. If this succeeds, further testing is unnecessary; if it fails, you at least know where the filter's interception boundary lies.
- Single-variable rewriting. Rewrite the same sentence using one form from the table above, record the result, return to the original sentence, and try the next form.
- Take indirect paths. Place the text in a document the application will retrieve rather than in the message you send directly. Many applications are careful about user input but assume retrieved content is safe.
- Focus on what the model can do, not just what it says. The model saying something it shouldn't is one thing; calling tools, modifying records, or exfiltrating data is another. The severity of a vulnerability depends on the model's permissions, not the tone of its reply.
- Repeat tests and count. Model behavior is probabilistic—one success doesn't prove the problem always exists, and one failure doesn't prove it doesn't. Run each test multiple times and report results as a rate, e.g., three successes out of ten.
- Read the logs. If the injected text can't be found in the logs, or has been sanitized beyond recognition, the security team cannot reconstruct the attack after the fact—that itself is a finding.
The example finding given in the original article: an attacker used the Arabic "ي" to bypass the input filter, succeeding seven times out of ten attempts; the model leaked its system prompt but did not call tools, so it was rated medium severity. The root cause was that the filter compared raw text directly with no normalization at the boundary. The fix: normalize once at the boundary, filter the normalized text, and preserve the original form in logs. After the fix, the same ten attempts yielded zero successes.
The Core of Defense: Normalize First, Then Filter
Most teams hand the raw text from the user straight to the filter—the correct order is exactly the opposite.
Before any decision is made, normalize the text once: map Arabic characters to their Persian equivalents, remove or expose zero-width and directional control characters, unify numerals, and apply a single Unicode normalization form. Then run filters and classifiers on the normalized text. Keep the original text for display so you don't lose what the user actually wrote.
Special emphasis: do not strip the zero-width non-joiner—it's part of correct Persian spelling. Remove only the invisible characters that serve no function in the text; show the rest to human reviewers in the review interface rather than deleting them.
Normalization doesn't eliminate the attack itself—it closes the cheapest bypass route and ensures the filters you write see what the model sees. And normalization should only be done once at the boundary—if different parts of the application each normalize separately, the discrepancies between them become new gaps.
Durable Controls That Don't Depend on the Model
Any defense that relies on the model behaving correctly may fail the day the model is updated. Truly durable controls live outside the model:
- Least privilege for tools. The model should only be able to touch the capabilities needed for the task through a whitelist, not based on general trust.
- Human approval for irreversible operations. Deletion, payment, and sending data outside the organization should not be triggered by a single sentence in a ticket.
- Treat model output as untrusted input. When output reaches a browser, query, or command, treat it the way you'd treat user input.
- Graded trust for retrieved content. Customer-uploaded documents and approved internal documents are not the same thing; the application should be able to distinguish them.
- Rate limiting and monitoring. Rate-limit tool calls and trigger alerts when usage patterns deviate from the norm.
Least privilege for tools is typically implemented by defining explicit parameter whitelists and scopes for each tool the model can call (e.g., only allowing reads from specific directories, only allowing requests to internal addresses) and enforcing validation at the tool execution layer rather than the prompt layer. Treating model output as untrusted input means output should undergo the same validation as user input before entering downstream systems—for example, if the model generates SQL or shell commands, syntax and permission checks should be performed before execution rather than directly concatenating and executing. Graded trust for retrieved content can be implemented via metadata: annotate each document with its source and approval status, attach trust labels when concatenating into context, and apply stricter filtering to low-trust content or restrict its ability to trigger tool calls. These controls don't depend on model behavior and therefore won't fail when the model is updated.
Four Questions to Ask Your Team or Vendor
| Question | Why it matters |
|---|---|
| Do the filters run on normalized text? | If not, a single Arabic "ي" gets through |
| What text besides user messages reaches the model? | Indirect injection comes in through these paths |
| What can the model do without human approval? | The severity of any finding depends on this permission |
| Can the original form of injected text be found in logs? | If not, the attack can't be reconstructed after the fact |
If you operate a chatbot, internal assistant, or agent that reads external text, it's worth spending one session with your team to list all these entry points. There's usually at least one route on the list that no one had thought of.
Originally published on hafezsecure.ir, where it is continuously updated.
One-line Summary
The Persian writing system's many variants can naturally bypass string filters; defense should normalize first, then filter, and layer controls outside the model.
Paragraph Summary
The article points out that the fundamental problem with prompt injection is that models cannot distinguish instructions from ordinary text, and indirect injection (through retrieved documents, tickets, file names, etc.) is more dangerous than direct injection. Persian's homoglyphs, zero-width characters, three numeral sets, Finglish, and directional control characters constitute five bypass forms that are semantically invariant to the model but invisible to string filters, rendering existing filters nearly useless. The author recommends normalizing at the boundary first (mapping homoglyphs, handling zero-width and directional characters, unifying numerals) before filtering, preserving the original text for display and logs, and not stripping the ZWNJ that Persian requires. Testing should be single-variable, repeated multiple times, and reported as success rates. Durable defenses must live outside the model: least privilege for tools, human approval for irreversible operations, treating model output as untrusted input, graded trust for retrieved content, and rate limiting plus monitoring.
Related articles

Chain of Thought Isn't Just for AI: How Humans Can Use CoT to Improve Thinking Quality
Chain of Thought isn't just a prompt trick for large models. Starting from a Hacker News discussion, this article analyzes why CoT works for human thinking too, breaks down actionable ways to transfer it, and highlights the key differences between human CoT and machine CoT.
Cloudflare Teams Up with OpenAI: Closi…
Cloudflare Teams Up with OpenAI: Closing the Loop from Vulnerability Discovery to Defense with AI
Cloudflare uses OpenAI Daybreak to investigate security vulnerabilities and generate reviewable remediation plans, while developers use Codex to boost coding efficiency and quality.

Orca Multi-Agent Orchestration in Practice: A Guide to Parallel Collaboration with Claude and Codex
Orca is Stable AI's free open-source agent workbench supporting 34 agents like Claude Code and Codex in parallel. Learn its Git worktree isolation, multi-model orchestration, and mobile management.