Web Security
Secure Rendering of LLM Output in Web Applications
Practical browser security guidance for rendering model-generated text, Markdown, links, code, HTML-like content, and structured output.
LLM output is untrusted data. It does not become safe HTML, JavaScript, a URL, or an authorized action because it came from an internal model, followed a system prompt, or was not typed directly by a user. A web application must classify, parse, validate, and render model output with context-specific defenses.
This guide focuses on browser rendering for text, Markdown, HTML-like fragments, links, code, JSON, and streamed responses. It complements the web security architecture.
The LLM output trust pipeline
Use an explicit pipeline:
Model → Output classification → Parse → Validate → Sanitize when needed → Safe rendering component → Browser
Classification defines the contract: plain text, a constrained Markdown subset, a URL, display-only code, or structured data. Parsing should be deterministic and bounded. Validation checks syntax and business rules. Sanitization is required when a rich format can contain active elements. The final renderer must match the content type.
Keep display separate from execution. Generated commands may be shown as code but must not run. A generated button label must not select a privileged operation. Tool proposals go through the server-side gateway in Secure Tool Calling for LLM Applications, not browser event handlers.
Plain text baseline
If formatting is unnecessary, render text nodes. Normal framework interpolation escapes characters for an HTML text context, keeping markup inert. Preserve line breaks with CSS or a text component instead of converting arbitrary text to HTML. Never concatenate model output into an HTML string, script block, style block, SQL statement, or shell command.
Text still needs length limits and privacy review. A model can produce enormous output, sensitive data, or misleading links. Rate and size limits protect the browser and logs. Content authorization determines whether a user may see text; escaping determines how the browser interprets it.
Markdown is a policy, not a guarantee
Markdown parsers differ in raw-HTML support, autolinks, embedded media, URL rewriting, plugins, and extensions. Configure the parser deliberately, pin its version, and prefer a small supported subset such as headings, emphasis, lists, tables, and fenced code.
If raw HTML is enabled, sanitize the parsed result before it reaches an HTML sink. Use an allowlist and remove active elements, event attributes, dangerous URLs, and unneeded attributes. Keep parser and sanitizer in one reviewed pipeline; a later transforming plugin can reintroduce risk.
Treat Markdown links as data. Validate the parsed destination, not only its visible label. Resolve relative application routes against the current origin and apply a policy to external links. Do not assume a harmless-looking link has a harmless protocol.
Raw HTML and XSS
Directly inserting model-generated HTML through a raw HTML escape hatch creates the same cross-site scripting risk as user-generated HTML. The model may reproduce content from a document, tool, or malicious prompt. A system instruction saying “never emit script” is not a browser control.
Use framework-safe text interpolation by default. If rich HTML is genuinely required, define allowed elements and attributes, sanitize at the last responsible boundary, and test the exact configuration. Context matters: HTML text, an attribute, a URL, CSS, and JavaScript each require different encoding rules. An HTML sanitizer does not make a string safe for a JavaScript or CSS context.
Content Security Policy is defense in depth. A carefully designed CSP can reduce impact from some injection paths, but it does not make unsafe rendering safe and does not validate model semantics.
Framework rendering principles
React and similar frameworks escape interpolated text by default but provide explicit APIs for raw markup. Treat those APIs as privileged sinks. Review the component that calls them, document its input contract, and make it difficult for ordinary model output to reach it accidentally.
Server-side rendering and client hydration must use the same content policy. A string sanitized on the server but parsed differently in the browser can produce inconsistent results. Avoid injecting streamed chunks into innerHTML; append structured text or render complete validated nodes. Keep custom Markdown components and third-party plugins under dependency review.
URLs and navigation
A generated URL may be a phishing destination, an unintended tenant route, or a dangerous URI scheme. Parse URLs with a standard URL parser, allow only product-supported protocols, and decide whether destinations must be same-origin or on an approved external list. Normalize before comparison. Use safe link components and apply appropriate rel attributes when opening a new context.
Visible link text is not evidence of its destination. For model-selected fetches or tools, browser rendering rules are insufficient; enforce server egress and authorization separately.
Code blocks are inert presentation
Render generated code as text inside a code or preformatted component. Syntax highlighting must treat code as data, not markup. Never evaluate code from a code block in the browser. Label copied code as generated and potentially unsafe. Sandboxing AI Agents and Generated Code covers controlled execution outside the browser.
JSON and structured output
Valid JSON is only a syntax result. Parse it, validate it against a strict schema, constrain lengths and enumerations, and reject unknown fields when appropriate. Map validated fields to known components; do not spread arbitrary keys into DOM properties or component props.
An object that says action: delete is still a proposal. Check principal, resource, tenant, and policy on the server before any side effect. Keep display and action execution separate, as described in Secure Tool Calling for LLM Applications.
Streaming output safely
Streaming changes timing, not trust. A partial chunk can split a delimiter, URL, Markdown token, or JSON string. Buffer until the unit required by the renderer is complete, then parse and validate it. If incomplete content cannot be classified safely, display it as escaped text until the stream finishes.
Do not execute event-stream data, treat a chunk as a DOM template, or let a client parser trigger tools. Bound stream length, cancellation, and concurrent connections. On errors, clear or quarantine partial state rather than mixing sessions. Bind the stream to its authenticated conversation and tenant for its full lifetime.
Server and client processing
There is no universal rule that parsing must be server-side or client-side. Choose the boundary with suitable libraries, performance, and trust context, then enforce one documented policy. Server processing can centralize configuration; client processing may support interactive rendering but increases dependency and consistency requirements.
Test the serialized result and final DOM. Cache keys must include tenant and content-policy versions. Never cache sanitized output under a key that can serve it to another user with different permissions.
Content type to safe handling matrix
| Output type | Primary risk | Recommended handling | Validation/sanitization | Execution permitted? |
|---|---|---|---|---|
| Plain text | HTML interpretation | Text node/component | Length and privacy checks | No |
| Markdown | Raw HTML, links, extensions | Restricted parser and renderer | Allowlist sanitizer and URL policy | No |
| HTML | XSS and active attributes | Avoid; otherwise rich renderer | Strict sanitizer and tests | No scripts |
| URL | Phishing or unsafe schemes | URL component with policy | Parse, normalize, protocol/origin allowlist | Navigation only if allowed |
| JSON | Unsafe props or actions | Schema-validated object to known UI | Strict schema and authorization | No automatic actions |
| Code | Accidental execution | Escaped code block | Size and dependency review | Never in browser |
Rich-content policy
Decide which generated classes the product supports. Plain text is easier to secure than arbitrary HTML. A Markdown subset can be useful when parser, sanitizer, URL policy, custom components, and tests are maintained together. Do not enable raw HTML, embedded frames, custom protocols, or plugin ecosystems merely because a parser offers them.
Document who owns the policy, how it changes, and which model versions were tested. A prompt change can alter output shape; treat it as a regression-triggering change rather than relying on an earlier rendering review.
Testing and observability
Test each supported type in the real browser and with server-rendered output. Include malformed Markdown, unexpected HTML, unusual URL encodings, oversized streams, invalid JSON, and output copied from untrusted retrieval sources. Verify code blocks remain inert, unknown schema fields are rejected, and display cannot invoke tools.
Record content type, parser and sanitizer versions, policy decision, correlation ID, and rendering outcome. Avoid logging full sensitive prompts or documents. For an incident, identify the model output, component, policy version, affected sessions, and whether content reached an active sink. AI Agent Observability provides a broader evidence model.
Practical checklist
- Treat every model response as untrusted data.
- Prefer escaped plain text when rich formatting is unnecessary.
- Configure a restricted Markdown dialect and review extensions.
- Disable raw HTML unless a tested allowlist sanitizer is required.
- Validate protocols, origins, and normalized destinations for links.
- Render code as inert text; never evaluate it in the browser.
- Parse and schema-validate JSON before mapping it to components.
- Keep displayed output separate from tool calls and state-changing actions.
- Buffer streamed fragments until safely classified.
- Apply CSP and security headers as defense in depth, not sanitization.
- Use one policy across server rendering, hydration, and client updates.
- Test the final DOM, dependencies, policy changes, and tenant-specific cache behavior.
Sources
- OWASP Cross Site Scripting Prevention Cheat Sheet
- OWASP DOM based XSS Prevention Cheat Sheet
- MDN Web Security and URL documentation
- React official DOM rendering and escaping documentation
- Official Markdown parser and sanitizer documentation
The durable rule is simple: models produce data, and trusted application code decides how that data is interpreted. Keep the browser at the end of a validated pipeline, never at the start of an implicit execution path.