← Blog

Insecure Output Handling Treats a Model Reply Like It Cannot Bite

Insecure output handling treats a model response as safe text once it has been generated, when the model is repeating whatever an attacker fed it upstream. In 2023, researcher Johann Rehberger showed that a ChatGPT plugin rendering a markdown image from model output would fetch an attacker-controlled URL automatically, leaking conversation data the moment the image loaded. The application trusted the output. Nothing downstream checked it again.

ByParminder Singh· Founder & CEO, DeepInspect Inc.
Problem-Awareai-securityllm-securitycybersecurityzero-trust
Insecure Output Handling Treats a Model Reply Like It Cannot Bite

In 2023, researcher Johann Rehberger demonstrated a data exfiltration technique against a ChatGPT plugin that required no exploit in the traditional sense. The plugin fetched a web page containing hidden instructions, the model followed those instructions and generated a markdown image tag pointing at an attacker's server with conversation data encoded in the URL, and the chat interface rendered that markdown automatically, causing the browser to request the image and hand the encoded data to the attacker. Nobody broke the model in any technical sense. The application simply treated the model's output as safe to render, the way it would treat any other trusted string, and the model was repeating exactly what the attacker had planted upstream.

TL;DR

  • Insecure output handling occurs when an application treats an LLM's response as trusted content instead of re-validating it before use.
  • A 2023 disclosure showed markdown images in model output triggering automatic, invisible data exfiltration in a production chatbot.
  • The same pattern generalizes to any downstream consumer: a browser rendering HTML, a database executing generated SQL, a shell executing a generated command.
  • The fix is treating every model output as attacker-influenced input to whatever consumes it next, not as a finished, safe result.

Why the trust assumption forms in the first place

Developers spend the early part of an integration worrying about what goes into a model: prompt injection, jailbreaks, data leakage in the request. What comes back out gets less scrutiny, because a model response looks like ordinary generated text, and text feels inert. That instinct is wrong specifically because a model faithfully reproduces patterns from whatever it processed, including content an attacker planted in a document, a web page, or a tool result the model read before answering.

The markdown-image case makes the mechanism concrete. The model did not need to be jailbroken or tricked into malicious intent. It followed instructions embedded in content it was asked to summarize, and it produced exactly the markdown syntax the attacker wanted, because generating markdown image syntax is an entirely ordinary thing for a model to do when asked. The vulnerability was never in the model's behavior. It was in the application rendering that output as live markdown without checking where the image URL pointed.

The same pattern, different downstream consumers

OWASP's Improper Output Handling category frames this as a general class, and the markdown-image case is one instance among several with the same shape. A chat interface that renders model output as HTML without sanitization is exposed to cross-site scripting if the model reproduces an attacker-planted script tag. An application that takes model-generated SQL and executes it directly against a database is exposed to injection if the model reproduces attacker-planted query fragments. A coding agent that executes model-generated shell commands without review is exposed to command injection through the same mechanism. In every case, the model is a faithful conduit for content it processed, and the vulnerability lives in whatever downstream system decided that model output did not need the same scrutiny as any other untrusted input.

Why "the model should refuse" is not the fix

A natural response is to expect the model itself to recognize and refuse to reproduce dangerous patterns, and providers have made real progress on refusal behavior for overtly malicious requests. That progress leaves this specific gap untouched, because the markdown-image case, and most insecure-output-handling failures generally, involve output that reads as benign in isolation. A markdown image tag looks ordinary on its own. Generated SQL looks ordinary on its own. The danger is entirely contextual, produced by what a specific downstream system does with that output automatically, and a model has no way to know how its output will be consumed once it leaves the response.

This is the same limitation that applies to model guardrails generally: they are trained behaviors evaluated at generation time, with no visibility into the execution context the output will land in a moment later. Output handling has to happen at that execution context, not inside the model.

What output handling actually requires

The concrete fix in the markdown case was specific and became standard practice afterward: restrict which domains a chat interface will render images from, so an attacker-controlled URL cannot silently trigger a request. The general version of that fix, applied across output types, is treating every model response as untrusted input to whatever processes it next: sanitize before rendering as HTML, parameterize rather than directly execute generated SQL, require review or a strict allowlist before executing generated commands. None of that is exotic. It is the same input-validation discipline applied for decades to any other untrusted string, applied consistently to a source, model output, that still gets treated as an exception by teams moving fast. The specific implementation patterns for each output type, schema validation, sanitization, and the four-layer umbrella that ties them together, are covered in depth here for teams ready to build the control rather than just recognize the risk.

DeepInspect

DeepInspect's contribution sits on the response side of the request it evaluates, inline between the model and the calling application. Every response passing back through DeepInspect can be inspected against policy before the application receives it: known-bad patterns, references to unapproved domains, content classified as carrying an embedded instruction, and the outcome is written to the same signed per-decision record as the request.

This is inspection at the network boundary, not a replacement for output handling inside the application itself. An application still has to sanitize before rendering, parameterize before executing, and review before running generated commands, because DeepInspect cannot see what a specific application does with a response after it arrives. What it adds is a policy checkpoint on the response before that application-level handling ever gets the chance to fail, and an audit trail showing exactly what the model actually returned when an incident needs to be reconstructed, the same structured record described in the audit log schema.

If your team validates what goes into a model carefully and treats what comes back as already safe, that asymmetry is worth closing. Book a demo today.

Frequently asked questions

Is insecure output handling the same vulnerability as prompt injection?

They are related but distinct. Prompt injection, covered at length here, is how an attacker gets malicious instructions in front of a model in the first place, often by hiding them in content the model processes. Insecure output handling is what happens after the model responds: whether the application treats that response as safe to use directly. The markdown-image case involved both: prompt injection delivered the malicious instruction, and insecure output handling let the resulting markdown trigger automatically. A system can have strong injection defenses and still be vulnerable if it does not validate output, and vice versa.

Does this risk only apply to chat interfaces that render markdown or HTML?

It is not limited to those interfaces. Any downstream system that acts on model output without independent validation is exposed, regardless of format. A backend service that parses a model's JSON output and uses a field from it to construct a database query, a file path, or an API call carries the same risk as a chat interface rendering markdown, because the validation gap is about trust, not about any specific rendering technology. Agentic systems that execute model-generated code or shell commands carry the highest-severity version of this risk, since the downstream action is direct code execution rather than a rendered image.

How is this different from validating user input, which most applications already do well?

The mechanism is the same discipline, applied to a source most teams have not yet built the habit of distrusting. Applications validate user input because decades of security incidents made that instinct automatic. Model output is new enough, and feels sufficiently machine-generated and authoritative, that the same instinct has not fully formed yet. The practical guidance is to treat model output with the same suspicion as user input, because from a downstream system's perspective, a model that processed untrusted content is effectively relaying that content, whether or not the two feel similar to a developer.

Are there tools that automatically catch insecure output handling before it ships?

Static analysis tools are starting to flag patterns like unsanitized model output flowing into a render or execute call, similar to how they flag unsanitized user input today, though tooling maturity here lags behind input-side prompt injection defenses. The more reliable current approach combines code review that specifically asks "where does this model's output go, and is it validated there" with runtime inspection of what a model actually returns in production, since static analysis catches known patterns but not every path model output can take through a complex application.