Interaction & Data Flow
Concrete carrier for the exfil-via-tool-output family (parent: MCP-005): an MCP tool returns Markdown content with image URLs built from caller-supplied or model-supplied data, letting an attacker exfiltrate context to a chosen domain via the image fetch. Sibling carrier MCP-221 covers the hyperlink variant.
Markdown image syntax (``) renders in most chat clients by issuing an HTTP GET. If any part of the URL is attacker-controllable, the rendering itself is the exfiltration channel — the user sees a broken image while the attacker sees their logs fill with conversation context.
MCP tool outputs are rendered directly by clients (Claude Desktop, Cursor, etc.). A tool that returns model-supplied data interpolated into image URLs creates a perfect channel: the attacker plants the payload in a document, the model retrieves it via a tool, the tool returns it as Markdown, the client renders the image, and the conversation context lands at attacker.example.
@server.tool() |
def render_link(label: str, target: str) -> str: |
# Caller controls 'target' — could be any URL. |
return f"" |
from urllib.parse import urlparse |
ALLOWED_HOSTS = {"images.your-cdn.example"} |
@server.tool() |
def render_link(label: str, target: str) -> str: |
if urlparse(target).hostname not in ALLOWED_HOSTS: |
raise ValueError("image host not allowed") |
safe_label = label.replace("]", "").replace("[", "") |
return f"" |
MCPSafe flags tool handlers that build Markdown image syntax (``) where the URL portion is constructed from a handler parameter via f-string, concatenation, or `.format()`. Builders that route through an allowlist or escape helper are exempted.
See the full threat catalog for every documented detection.
MCPSafe runs this check — and every other rule in the catalog — on any MCP server you paste in.
Scan now