Blog · XSS · 2026-08-27

AI-generated code and XSS: an old bug, back at scale

Cross-site scripting was supposed to be a solved problem — templating engines escape by default, React escapes by default, everyone knows not to trust user input. Independent testing of AI-generated code across major LLMs has found XSS-prone output in the majority of samples anyway. Here's the mechanism, including a second, newer version this generation of apps introduced on its own.

Safe by default

Text rendered through normal JSX ({value}), React's default output path. It's automatically escaped — a user typing <script> sees it rendered as harmless text, not executed.

The opt-out

dangerouslySetInnerHTML — or the equivalent in any other framework — inserts a string directly as HTML, no escaping, no questions asked. Whatever is in that string runs.

Two different sources of the same bug

The classic version is what security teams have tested for since the early 2000s: untrusted user input — a comment, a profile bio, a search query — gets reflected back into the page, or stored and rendered later, without being escaped first. AI coding assistants reproduce this constantly, particularly when a feature explicitly needs "rich text" and the fastest working implementation an assistant reaches for is rendering a raw string as HTML rather than running it through a sanitizer.

The newer version is specific to this generation of apps: AI chat features and AI-generated content rendered directly into the page. An app that lets a model answer a user's question, then renders that answer with dangerouslySetInnerHTML to support formatting (bold text, links, code blocks) has built exactly the same vulnerability with a different source of untrusted content — the model's output instead of a human's. If anything upstream of that output can be influenced by a user (their own message, a document they uploaded, a support ticket the model summarizes), that influence can carry through to what ends up unsanitized in the DOM.

Why the numbers are this bad

Independent benchmarking of AI-generated code against the OWASP Top 10 has repeatedly found XSS as one of the most common categories of failure, and testing across the major coding-assistant models specifically has found XSS-prone patterns in the large majority of samples that included any HTML rendering at all. Pass rates aren't uniform — testing has found meaningfully better results in Python-generated code than in Java generated for the same task, which tracks with how much raw string-concatenation and manual HTML assembly shows up as "normal" code in each language's usual patterns.

None of this means the underlying models can't write safe code — they can, and often do when explicitly asked to. It means the default output, for the prompt an actual founder types ("show the user's comment with formatting," "display the AI's answer with markdown"), skews toward the fast, unsafe path more often than it should.

How to check your own app

Search your codebase for every instance of dangerouslySetInnerHTML (or v-html in Vue, [innerHTML] in Angular). For each one, trace the string being inserted back to its source. If that source is ever a user input, an uploaded file, or an AI model's response built from anything a user influenced — and there's no sanitizer call between the source and the render — that's a live vulnerability, not a hypothetical one.

For the classic version, test every input field and query parameter with a standard payload like <img src=x onerror=alert(document.cookie)> and check whether it executes when the page containing that input reloads.

The fix

Default to escaped output everywhere. Where real HTML rendering is a genuine requirement — rich text, markdown, AI chat responses — run the string through an allowlist-based sanitizer immediately before it reaches the render, every single time, with no exceptions for "trusted" sources:

import DOMPurify from "dompurify";

// Never render a raw string as HTML — sanitize immediately before render,
// even when the source is "your own" AI model's response.
function AiResponse({ html }: { html: string }) {
  const clean = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ["b", "i", "code", "pre", "a", "ul", "ol", "li"],
    ALLOWED_ATTR: ["href"],
  });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Use an allowlist of specific tags and attributes you actually need, not a denylist of things you're trying to block — denylists miss the next bypass technique by construction, allowlists don't. If a markdown renderer is doing the formatting instead, confirm it produces sanitized HTML by default; several popular ones do not unless explicitly configured to.

Is your AI feature's output actually sanitized?

StackSecured's Insecure LLM Output engine checks for AI-generated content rendered via dangerouslySetInnerHTML with no sanitizer present — the AI-specific version of this bug — alongside classic reflected XSS testing on every input.

Run a free scan

Common questions

Why does XSS still happen when every modern framework escapes output by default?

+

Because the default only holds until a developer — or an AI assistant — deliberately opts out of it, usually to render something that needs real HTML formatting: a rich-text comment, markdown, or an AI-generated response with bold text and links. React escapes everything by default; dangerouslySetInnerHTML is the explicit opt-out, and it does exactly what its name says. The vulnerability isn't the framework failing — it's code that opts out of the safe default without adding a replacement for what that default was doing.

Is AI-generated output actually attacker-controlled? The AI is on our side.

+

Not always — but often enough to matter. If your app builds a prompt from anything a user can influence (a support ticket, a product review, a chat message) and later renders the model's response as HTML, an attacker who can shape the input can sometimes shape the output too, especially combined with prompt injection. Even without that, a compromised or manipulated upstream data source feeding your AI feature has the same effect: content you don't fully control ending up unsanitized in the DOM.

Does escaping by default hurt features like markdown rendering in an AI chat UI?

+

No — the fix isn't 'never render formatted text,' it's 'never render raw, unsanitized HTML.' Markdown renderers that convert to safe HTML, or an allowlist-based sanitizer run on any HTML before it reaches dangerouslySetInnerHTML, give you the formatting without giving up the safety. The mistake is skipping that step entirely, not using rich formatting at all.

Does this differ by which AI coding tool or model I used?

+

Independent testing across major LLMs and coding assistants has found XSS-prone patterns across all of them at meaningfully high rates — this isn't a single-tool problem you can dodge by switching assistants. Pass rates also vary by target language; Python-generated code has tended to test safer than Java in the same benchmarks, likely reflecting how much unsafe string-concatenation and raw-HTML-assembly boilerplate shows up in each language's typical training data.

More reading: Broken access control · Prompt injection in AI features · Full vibe-coding security checklist