> ## Content Index
> Fetch the complete content index at: https://debugly.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# The Model Output That Rendered as HTML in the Browser
- URL: https://debugly.dev/model-output-rendered-as-html/
- Published: 2026-10-09T11:33:00.000Z
- Updated: 2026-10-17T05:56:53.000Z
- Description: The assistant's reply was trusted because a model wrote it, and the reply was inserted with innerHTML, so a crafted input became stored script, and the…
- Author: Rohit Bhadani
- Tags: Security, AI, Frontend

I started at the persistence, because that is where the symptom landed, and walked it backwards link by link until the ran stopped looking innocent.

The model output rendered as HTML has a single weak property, and here it is: the reply is saved, so the payload persists and executes for every subsequent reader, and the readers are the other users, and the other users are the blast radius.

This was a support assistant, and every feature that renders model output into the DOM behaves the same way.

## Why the output was trusted

The output came from the model, and the model is the assistant, and the assistant is the feature, and the feature is trusted, and the trust is the assumption, and the assumption is that the model produces safe text, and the safe text is the assumption's content, and the content is wrong, because the model produces text derived from the input, and the input is the user's, and the user's input can contain anything, and the anything includes the script, and the script is the payload, and the payload is in the model's output, and the output is the reply, and the reply is rendered, and the rendered is the execution, and the execution is the incident.

The trust is the misplacement, because the model is not the source of the content, the model is the transformer, and the transformer's output inherits the input's properties, and the input's properties include the attacker's payload, and the payload is the injection, and the injection is the incident, and the incident is the reason the trust is misplaced, because the trusted thing is the transformer, and the transformer is not the source, and the source is the attacker.

The innerHTML is the execution, because the innerHTML parses the string as HTML, and the parsed HTML includes the script, and the script is the execution, and the execution is the incident, and the incident is the reason the innerHTML is the vulnerability, because the innerHTML is the parse, and the parse is the trust, and the trust is the misplacement.

## Why the model echoed the payload

The model was asked to summarise the conversation, and the conversation included the user's message, and the message included the script, and the script was in the context, and the context is the model's input, and the input is echoed, because the summary includes the content, and the content is the script, and the script is the echo, and the echo is the payload, and the payload is the output, and the output is the reply, and the reply is the stored, and the stored is the persistence.

The model is not malicious, the model is faithful, and the faithfulness is the echo, and the echo is the payload, and the payload is the incident, and the incident is the reason the faithfulness is the vulnerability, because the faithful reproduction of the attacker's input is the attacker's payload in the output, and the output is rendered, and the rendered is the execution.

This is the same injection as [the prompt injection that leaked the system prompt](https://debugly.dev/prompt-injection-leaked-system-prompt/), and the common root is the property, not the platform.

## The fix, in order

**Never insert model output with innerHTML.** The output is inserted as text, and the text is the safe, and the safe is the fix, and the fix is the textContent, and the textContent is the one-line change, and the change is the prevention, because the text is not parsed, and the not parsed is the not executed, and the not executed is the fix.

**Sanitise before rendering.** Where the output must be rendered as HTML, the markdown, the sanitiser strips the script, and the stripped is the safe, and the safe is the fix, and the fix is the sanitiser, and the sanitiser is the allowlist, and the allowlist is the control, and the control is the discipline, because the allowlist permits the formatting and rejects the script, and the rejection is the prevention.

**Treat the model output as untrusted input.** The output is the same trust level as the user's input, because the output is derived from the input, and the derived is the inherited, and the inherited is the untrusted, and the untrusted is the sanitisation's requirement, and the requirement is the fix, and the fix is the discipline, because the output is not the model's, it is the input's, and the input's is the attacker's.

**Apply a Content Security Policy.** The CSP is the defence in depth, and the depth is the second layer, and the second layer is the block, and the block is the script's failure, and the failure is the mitigation, and the mitigation is the reduction, and the reduction is the blast radius, and the radius is the fix, because the CSP does not prevent the injection, it prevents the execution, and the prevented execution is the mitigated incident.

**Escape at the boundary.** The rendering layer escapes, and the escaping is the boundary's control, and the control is the fix, and the fix is the framework's default, and the default is the React's text rendering, and the text rendering is the safe, and the safe is the fix, and the fix is the reason to use the framework's default rather than the raw HTML, and the raw HTML is the innerHTML, and the innerHTML is the vulnerability.

## The takeaway

Model output is derived from user input, so it inherits the input's trust level, and inserting it with innerHTML parses the attacker's payload as HTML, which executes for every reader of the stored reply. Render as text, sanitise when HTML is required, treat the output as untrusted, apply a CSP, and escape at the boundary.

The assistant's reply ran a script, and the script was the user's, echoed by a model that was faithful to its input, and the faithfulness was the payload's delivery. Name that one property in review and this class of bug gets hard to ship.

---

Published on [debugly.dev](https://debugly.dev/) by [Rohit Bhadani](https://debugly.dev/about/) | Founder of [Krova Cloud](https://krova.cloud/?ref=debugly.dev)