Context EngineeringLesson 72 of 87

Contents

Context Engineering

Lesson 72 of 87. Sign in to keep your place.

Module 8 · Untrusted text and the trust boundary

The outward channels you did not know you had

9 min read

A channel does not need to look like sending

When a team says the assistant cannot send anything, they mean it has no send_email tool. That is one channel out of several, and usually not the one that gets used.

The general form is broader and worth memorising: anything that causes a request to a URL whose contents an attacker can influence is a channel out. The request itself carries the data, in the path or the query string. Nobody needs to receive a message; the attacker reads their own server’s access log.

Rendered markdown images

This is the classic, it has been found in a long list of shipped products, and it is worth understanding precisely because the mechanism is so ordinary.

Most chat interfaces render the model’s markdown output. If that includes an image reference, the client fetches the image so it can display it. That fetch is an HTTP request from the user’s browser to whatever host the URL names, and the URL was written by the model.

If an injected instruction persuades the model to include the private data in the path of an image URL, the user’s own browser delivers it to the attacker’s server on render. The image does not even have to exist: a 404 is logged just as usefully as a 200. The user sees a broken image icon, or nothing at all if it is a one-pixel transparent reference, and no action was required of them.

The defences are all in the client, not the model:

  • Do not auto-render remote images. Render only images you host, or show a placeholder the user must click.
  • Enforce a Content Security Policy that restricts img-src to an allowlist of hosts.
  • Proxy images through your own server with a host allowlist.

All three are cheap. The reason this bug recurs is not difficulty; it is that image rendering is a display feature owned by a front-end team and exfiltration is a security concern owned by someone else, and the two lists never got compared.

Links the user clicks

Weaker, because it needs a click, and still effective, because a plausible link in a helpful answer gets clicked. The same defences apply: allowlist the hosts you will render as links, show the full destination, and consider not making model-authored URLs clickable at all when the turn involved untrusted content.

Tools that fetch

A fetch_url or browse tool is a direct channel out, more reliable than the image because no rendering and no user is involved. So is anything that resolves a hostname, posts a webhook, or writes to a URL.

If the assistant must fetch, an allowlist of permitted hosts converts the channel from open to closed. This is a real constraint on the product (the assistant can no longer follow a link to an arbitrary site) and it is the constraint, not a prompt, that provides the security property.

The ones that surprise people

A search tool. The query is attacker-chosen text sent to a third party and typically logged. If the query can contain the private data, the search provider’s logs are the drop point. A tool need not fetch a URL to be a channel; it only needs to transmit a string somewhere.

Any tool argument at all. Creating a calendar event, filing a ticket, writing a file to a shared location, setting a display name: each one moves attacker-chosen text into somewhere the attacker may be able to read. The channel is not the tool’s purpose but its reachability.

DNS. A hostname lookup for <data>.attacker.example reaches the attacker’s authoritative name server even if the HTTP request is blocked by a firewall. This is why a network allowlist that permits resolution but blocks connection is not the control it appears to be.

Error messages and retries. A tool that echoes its failed URL into a log the attacker can read is a channel with extra steps.

Auditing yours

List every way a string produced by the model reaches something outside your process. Not every tool: every path. Include the rendering layer, because the rendering layer is where the recurring bug lives and it is usually not in the list someone hands you.

For each path, answer two questions. Can the destination be influenced by the model, or is it fixed by your code? Can the content include arbitrary text, or is it constrained to values you generate?

A path where both answers are “fixed and constrained” is not a channel. A path where either is open, is. In most systems the audit turns up two or three paths nobody had counted, and the front-end markdown renderer is on that list far more often than not.

The honest limit

You will not close every channel, and a determined attacker with an assistant that can act in the world has a wide surface. The point of the audit is not perfection. It is that closing the three obvious channels converts a trivial attack into a difficult one, and difficulty is a real security property even though it is an unsatisfying one to state.

Every lesson here is open. Your progress is recorded once you pass the Data, SQL and Getting to the Answer exam.

Check your understanding

A chat client renders the model’s markdown, including remote images. Why is that a data egress path even with no send tool and no user click?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minLabelling every block with where it came from

Stuck on something?Ask Sayali, your AI tutor

How to cite: Addaly (2026) ‘The outward channels you did not know you had’, in Context Engineering. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/context-engineering/exfiltration-channels (Accessed: 13 October 2026).