← Blog

Insecure Output Handling Treats a Model Reply Like It Cannot Bite

Parminder Singh
Parminder Singh··5 min read
Summarize with AI

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.

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 can execute an attacker-planted script tag. An application that passes model-generated SQL directly to a database can run attacker-planted query fragments. A coding agent that runs model-generated shell commands without review can execute the same pattern at the host. In every case, the model is a faithful conduit for content it processed, and the vulnerability lives in the downstream system that treated model output as trusted 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. These controls are established input-validation practice, applied to a source that teams still too often treat as an exception while 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.

A prevention review for the response path

Start with the exact consumer of the response. A chat renderer needs an explicit markdown and URL policy. A JSON consumer needs a schema that rejects unexpected fields before business logic sees them. A component that proposes a command, query, or external request needs a narrow allowlist plus a human or service authorization decision before it acts. The record to retain is the response identifier, consuming component, validation result, and any blocked destination or field.

Then test the failure path with attacker-controlled content. Rehberger's 2023 markdown-image demonstration remains a useful fixture because a browser request is visible in a proxy log and the expected result is unambiguous: the renderer rejects the external image URL. For SQL, the test fixture should reach a parameterized query path and return no extra rows. These tests expose the point where a model response crosses into an execution-capable component, which is the handoff a production review needs to protect.

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 needs sanitization at rendering time, parameterized database calls, and a review or allowlist for 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?

The two risks involve separate control points. 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 pairs code review of each response consumer with runtime inspection of production responses. Static analysis catches known patterns; a production review reveals the paths an attacker-controlled response can take through a complex application.