Worked fix: sanitising output before it renders

Lesson 5 of 5 in The Guardrail Catalog: Runtime Checks, Mapped Per Cloud.

Catalog row 8 gets its own lesson because it is the cheapest entry in the catalog and the one whose absence has produced the largest number of published agent data-leak disclosures.

The mechanism is simple enough to state in one sentence, and a defender needs nothing more detailed than this. Markdown and HTML contain constructs that make the rendering client fetch a URL automatically — an image tag being the classic one. If an agent can be induced to put attacker-chosen text into a URL inside its output, and your UI renders that output as markup, then rendering the answer performs the exfiltration. No click, no download, no malware. The trifecta’s third leg — external communication — turns out to have been sitting in your renderer the whole time.

That is why this row pairs with a containment truth: the same chain needs an injection to steer the agent and private data in context, so injection filtering and least-privilege context both cut it too. But sanitisation is the layer that closes the channel, and closing the channel is what the vendors below ended up doing.

The fixes, and the ladder they climb. Note how each vendor’s answer is more restrictive than the last.
DisclosureInjection entry pointExfiltration channelThe fix that shippedWhat it teaches

Slack AI (2024-08-20)

Message in an attacker-created one-member public channel, retrieved as context.

Rendered markdown link with the secret as a URL parameter — required a victim click.

Slack said it deployed a patch; specifics were not in the disclosure. Its stated position was that public-channel visibility is intended behaviour.

“It needs a click” is not a mitigation. Users click links that say reauthenticate.

GitLab Duo (2025-05-22)

Hidden prompts in MR descriptions, comments, commit messages, issues, source code.

Untrusted HTML rendered in Duo’s responses.

Patch prevented Duo rendering unsafe HTML tags such as img or form pointing to domains outside gitlab.com.

Tag-level sanitisation plus a host allowlist — the first genuinely structural fix on this list.

AgentFlayer (2025-08-06)

Uploaded document containing an invisible (1px, white) injection.

Rendered image URL with secrets as parameters. Zero-click.

Bypassed an existing url_safe image-rendering mitigation by hosting the exfil image on Azure Blob Storage and reading leaked parameters from Azure Log Analytics request logs.

Reputation-based allowlisting fails. “Is this host trustworthy?” is the wrong question; “may model output cause any fetch?” is the right one.

ForcedLeak (2025-09-25)

Web-to-Lead form Description field (42,000 characters of attacker-controlled text).

Image URL to a domain on Salesforce’s CSP image allowlist that had expired and was purchasable.

Salesforce re-secured the expired domain and released patches enforcing Trusted URLs, so agent output cannot be sent to untrusted URLs.

An allowlist is an asset with an expiry date and an owner. Nobody was watching the registration.

CamoLeak (2025-10-08)

Pull-request description using GitHub’s invisible-comments feature.

GitHub’s own Camo image proxy: a pre-generated dictionary of signed URLs, one per character, rendered as invisible images.

GitHub disabled image rendering in Copilot Chat entirely.

Your own signed proxy is not a shield if the signatures can be minted in advance. The end of the ladder is removing the capability.

So write the catalog entry properly. Risk: improper output handling (OWASP LLM05) used as an exfiltration channel. Check: strip or neutralise every construct that can cause an automatic fetch — image tags, forms, iframes, CSS url(), SVG references, link previews — and allowlist any host you still permit, by explicit registration you control. Where it runs: at the rendering boundary, in every channel that renders model output. Cost: microseconds, no model call, no false positives worth the name.

The phrase doing the work is every channel. Teams sanitise the main chat pane and forget that the same agent output reaches a PR comment, an email, a Slack message with link unfurling, or a webhook consumer that renders HTML. Enumerate your render surfaces before you tune your thresholds.

One nuance that changes the design, and that the disclosures above make concrete: with an image-tag channel, the outbound request is made by the victim’s browser, not by your agent’s host. Server-side egress controls do not close it. They do close the service-side variant, where the agent itself fetches the attacker URL — so you need both, and you need to know which one each control covers.

Interactive sorting exercise: Eight proposed responses to the markdown-image exfiltration channel. Which actually close it, which only narrow it, and which do not close it at all?

Interactive flashcard deck.

Interactive checkpoint quiz (2 questions) — open this page in a browser to take it.