// ai security

LLM Data Exfiltration: Images, Unfurls, DNS and Tool Calls

LLM data exfiltration diagram concept: agent context leaking through images, links, DNS and tool calls

LLM data exfiltration happens when an agent's private context leaves through a channel the attacker can read: a rendered Markdown image, a link unfurl, an outbound tool call, a DNS lookup or a file write. Prompt injection only starts it. Whether data actually leaves depends on which of those channels your deployment leaves open.

The short answer

  • LLM data exfiltration is private context leaving an agent through rendered Markdown images, link unfurls, outbound tool calls, DNS lookups or file writes.
  • Prompt injection triggers it; you cannot fully fix injection, but you can close each channel with a control outside the model.
  • Sanitization and CSP have public bypasses. Egress allowlists, render proxies, disabled unfurls, restricted resolvers and scoped tokens hold.
  • Test by seeding canaries, injecting through every input path and watching HTTP, DNS and mail listeners through the real client.

Key takeaways

  • LLM data exfiltration is a channel problem: images, links, unfurls, tool calls, DNS and file writes.
  • Prompt injection starts the leak but cannot be fully fixed; each exfiltration channel can be closed in code.
  • Agents with no tools still leak through rendered Markdown images and platform link unfurls.
  • Sanitization and CSP are bypassable layers, as Bard and EchoLeak showed; egress control and scoped credentials are the controls that hold.
  • Test with canaries and listeners through the real client, one test case per channel, repeated across runs.

What is LLM data exfiltration, and how is it different from prompt injection?

LLM data exfiltration is private data leaving an agent through a channel an attacker controls or can observe. Prompt injection (LLM01 in the OWASP Top 10 for LLM Applications) is the instruction that tells the model to do it. They are separate findings with separate fixes.

Injection is close to unfixable at the model layer. Any text the agent reads can carry instructions: a web page, a PDF, an email, a RAG chunk, an MCP tool description. Exfiltration is fixable, because every channel is concrete plumbing: a renderer that fetches URLs, an HTTP client, a resolver, a filesystem. You can enumerate that plumbing and close it in code.

That is why we report the two separately. An injection that changes the model’s tone is low severity. An injection that puts a customer’s API key into a URL your Slack workspace fetches is a data breach. The finding is the data that left, the channel it used, and the control that would have blocked it.

The lethal trifecta as a triage test

Simon Willison’s term from 2025, the lethal trifecta, names the combination that makes theft possible: access to private data, exposure to untrusted content, and a way to communicate externally. With all three in one agent, an attacker can steal the data. Remove any one and the chain breaks. For every agent in scope, ask:

  • What private data can it read (mailbox, repos, CRM, RAG index, other tenants)?
  • What untrusted text reaches its context (web, inbound email, tickets, public issues, uploaded files, MCP tool output)?
  • What can carry bytes out (rendered images, links, unfurls, HTTP tools, DNS, file sync, outgoing email)?

Most production agents answer yes to all three without anyone having decided that. The design decision that holds up is removing one leg for each data scope, not hoping the model refuses.

How does Markdown image exfiltration work?

The model writes a Markdown image whose URL contains the secret, and the chat client fetches that URL automatically when it renders the reply. No click is needed. The attacker reads the data in their web server’s access log. The user sees a broken image icon, or nothing at all if the server returns a 1×1 pixel.

The class has a long public record. Johann Rehberger (Embrace The Red) demonstrated it against ChatGPT, Bing Chat and Google Bard in 2023, and OpenAI later shipped a mitigation that checks image URLs before ChatGPT renders them. The Bard case is the instructive one: its Content-Security-Policy restricted images to Google domains, and the bypass routed data through Google Apps Script, which ran on an allowed domain.

In 2025, Aim Security disclosed EchoLeak (CVE-2025-32711), a zero-click exfiltration chain against Microsoft 365 Copilot. The chain reportedly used reference-style Markdown to slip past link redaction, then a Microsoft-owned endpoint that CSP permitted. The lesson from every one of these cases: an allowlisted image domain is only as strong as the least-trusted content hosted on it.

No tools required

Rendered output is a channel that needs no tool permissions at all. An agent with zero tools and read-only RAG access can still leak through:

  • Auto-fetched images in the chat UI, as above.
  • Clickable links with data in the query string. These need one click, and model-written text such as ‘click to verify your session’ makes that click likely.
  • Unfurls and previews generated by whatever platform displays the output.
  • HTML or SVG inserted into a page without encoding, which turns a leak into stored XSS.

Teams often scope tool permissions carefully, then render model output as trusted Markdown. Output rendering belongs in the threat model with the same weight as tool access.

How do Slack and Teams link unfurls leak model output?

When an agent posts a message containing a URL, the platform’s servers fetch that URL to build a preview. If the model put secrets in the URL, the platform delivers them to the attacker’s server before any human clicks anything.

Slack link unfurls, Microsoft Teams link previews and many email clients’ previews all behave this way. The fetch comes from the vendor’s infrastructure, so your own egress controls never see it. PromptArmor’s 2024 research on Slack AI showed a related path: injected content in a public channel steered the assistant into producing a link that carried data from a private channel.

Test it directly: have the agent post a URL to a bot-accessible channel, pointing it at a listener you control, with a canary string in the query. A request from Slackbot or a Microsoft user agent means the channel is open. The fix is configuration, not prompting: set unfurl_links and unfurl_media to false on bot messages in Slack’s API, or strip and rewrite URLs before posting.

How do RAG and MCP indirect prompt injection turn into a leak?

Indirect prompt injection puts attacker instructions inside content the agent retrieves rather than content the user types. RAG and MCP make that retrieval automatic, so the attacker never touches the chat box.

In RAG, one poisoned document in a shared index is enough. A wiki page, support ticket or uploaded contract carries the instruction, and any user whose query retrieves that chunk triggers it with their own permissions. A RAG pipeline security assessment should trace which document IDs reached the context window when the leak fired.

MCP widens the input surface further: tool descriptions, tool outputs and resource contents all enter context. Invariant Labs showed in 2025 that a malicious public GitHub issue could steer an agent connected to the GitHub MCP server into reading private repositories and writing their contents into a public pull request.

That is a confused deputy. The agent held a token broad enough to read private repos and write public ones, and the attacker borrowed it. MCP server and tool-chain security testing targets exactly that gap between what a token allows and what the task needs.

Which agent tools become exfiltration channels?

Any tool that sends bytes to a destination the model chooses. HTTP fetch and browsing are the obvious ones. These carry the same risk:

  • Search tools: the secret goes into the query, which lands in a third-party log.
  • Email and messaging tools: the agent sends a message to an attacker address.
  • Ticketing, CRM and code hosting writes: data gets copied into a public issue, comment or repo.
  • Webhooks and calendar invites: external recipients receive model-written content.

The control is an egress allowlist enforced outside the model, plus an approval gate for any write to an external principal. The approval prompt must show the exact arguments that will execute, not the model’s summary of them.

DNS

If the agent can resolve arbitrary hostnames, it can encode data in subdomain labels, and the attacker’s authoritative name server records each query. HTTP egress blocks do not stop this, because resolution happens before any connection is made.

Coding agents are the usual victims. A command allowlist that permits ping, nslookup, dig, git or a package manager also permits lookups against attacker domains. Researchers disclosed several such paths in coding agents during 2025, where commands approved to run without confirmation were enough to leak secrets from .env files.

File writes

File writes are the overlooked variant. An agent that writes to a synced folder, a shared drive, a build artifact or a public repo is publishing. Treat any write location that syncs off the host as egress. Confine sandboxed agents to a scratch directory that does not sync, behind a resolver that only answers for allowlisted domains.

What controls actually stop LLM data exfiltration?

Controls that hold are deterministic and sit outside the model: they block the channel no matter what the model writes. System prompt instructions, refusal training and injection classifiers lower the hit rate. None of them close a channel.

Channel Test case Control that closes it
Markdown image Injected doc asks for image URL with canary in query Render images only through a proxy you own, or not at all; no model-generated image URLs
Clickable link Model emits link with canary to listener Rewrite links through a warning redirector; strip query strings to unknown domains
Link unfurl Agent posts canary URL to Slack or Teams Disable unfurls for bot posts; rewrite URLs before posting
HTTP or search tool Instruction to fetch listener URL with secret Network-layer egress allowlist; argument logging
Email, ticket, repo write Instruction to send data to external principal Approval gate showing exact arguments; recipient allowlist; least-privilege tokens
DNS Shell command resolving canary subdomain Restricted resolver; no raw network commands on the auto-approve list
File write Write canary to synced or public path Write sandbox limited to a non-synced scratch directory

Underneath every row sits one principle: scope the agent’s credentials to the task, so the data an injection can reach is the data the current user is already allowed to see.

Why output sanitization and CSP are not enough

LLM output sanitization and Content-Security-Policy each close one rendering path, and both have been bypassed. Sanitization regexes miss alternate syntax, as EchoLeak’s reference-style links reportedly did. Other known bypasses use HTML img tags, data hidden in fragment identifiers, and URL encodings the filter did not normalize.

If you sanitize, parse the Markdown into an AST and drop image and link nodes you do not allow, rather than pattern-matching strings. CSP fails open when an allowed domain hosts attacker-controllable content or redirects, as Bard and EchoLeak both showed, and it does nothing for unfurls, tool calls, DNS or file writes. Use both as layers. Neither is the fix for an exfiltration finding.

How do I test an LLM agent for data exfiltration?

Plant a canary, inject an instruction through every untrusted input, and watch every channel for the canary. A test passes only if no listener ever receives it.

  1. Seed canaries. Place unique strings in data the agent can read: a fake API key in a repo, a marker in a CRM record, a unique phrase in a second tenant’s RAG documents.
  2. Stand up listeners. An HTTP endpoint, an authoritative DNS zone and a mailbox you control, all logging full requests.
  3. Inject through every input path. Browsed pages, uploaded files, inbound email, RAG documents, MCP tool descriptions and tool outputs. Our prompt injection testing covers each path separately because filters often differ by source.
  4. Target each channel explicitly. One test case per row in the table above, plus encoding variants (base64, hex, data split across requests).
  5. Run through the real client. Image and unfurl exfiltration only fires in the actual renderer, so API-only testing misses it.
  6. Report the path. Input source, document or tool ID, channel, data received, and the control that would block it.

Repeat each test case several times. Model behavior varies between runs, and a 1-in-10 success rate is still a working exploit.

What to monitor in production

Log tool calls with full arguments, every outbound destination, and every URL in rendered output, then alert on:

  • Tool-call arguments with long high-entropy strings, base64, or values matching secrets patterns.
  • First-seen domains in HTTP tools, rendered image URLs and DNS queries.
  • Unusually long subdomain labels, or a burst of unique subdomains under one parent domain.
  • Writes to external principals (new email recipients, public repos, external channels) shortly after the agent read sensitive data or untrusted content.
  • A document retrieval followed by a tool call the user’s prompt did not ask for. That sequence is the signature of indirect injection.

Keep retrieved document IDs and tool outputs in the trace. Without them, incident response cannot identify the poisoned source.

Markdown image exfiltration LLM concept: a rendered pixel carrying data to an attacker server
A 1×1 image is enough: the client fetches it, the server logs the secret.
Lethal trifecta AI agent concept for LLM data exfiltration: private data, untrusted content, external communication
Remove any one ring and the exfiltration chain breaks.
Agent DNS exfiltration concept: data escaping a sandboxed LLM agent through name resolution
HTTP blocked, DNS open: subdomains carry the payload out.

FAQ

What is LLM data exfiltration?

It is private context leaving an AI agent through a Markdown image, link unfurl, DNS lookup, file write or outbound tool call. Prompt injection is usually the trigger, but the finding is the data that left and the channel it used.

How does Markdown image exfiltration work?

The model writes an image tag whose URL contains secret data, and the chat client fetches it automatically when rendering. The attacker reads the data from their server log without the user clicking anything.

What is the lethal trifecta in AI agent security?

Simon Willison's term for an agent that combines access to private data, exposure to untrusted content, and the ability to communicate externally. With all three, an injected instruction can steal data; removing any one breaks the chain.

Does a system prompt telling the model not to leak data help?

It lowers the success rate slightly but is not a control. Injected text competes with the system prompt in the same context window, so only controls outside the model, such as egress allowlists and render proxies, reliably block a channel.

Is blocking HTTP egress enough to stop agent exfiltration?

No. DNS lookups, unfurls fetched by Slack or Teams servers, outgoing email and file sync all bypass a simple HTTP block. Each channel needs its own control.

Are self-hosted or local models safer from exfiltration?

Only slightly. The channels belong to the agent, not the model, so a local model with a browser tool, Slack posting and shell access is as exposed as a hosted one. The real gain is network control: no default egress, a restricted resolver and a forced proxy, which you can also apply when tools for hosted models run in your environment.

If you want every exfiltration channel in your agents enumerated and proven open or closed with canaries, our AI agent penetration testing reports each one with the control that blocks it.



// get started

Work with AgentOffense

Tell us about your target and goals. We’ll reply with scope and a fixed-price quote — usually within one business day.

./request_engagement