XSS Prevention Guide: Protect Your AI-Built App from Cross-Site Scripting
Cross-site scripting (XSS) lets attackers inject malicious scripts into your app. Here's how to find and fix XSS vulnerabilities in AI-generated code.
By Gabriel CA · Kraftwire Software
· 9 min readWhat Is Cross-Site Scripting (XSS)?
Cross-site scripting is an attack where malicious JavaScript is injected into your web application. When another user visits the page, the attacker's script runs in their browser, stealing cookies, session tokens, personal data, or redirecting them to phishing sites.
XSS is consistently in the OWASP Top 10 and is one of the most common vulnerabilities in AI-built applications. It is also one of the most misunderstood. Many developers assume that modern frameworks like React prevent XSS automatically. That is partially true, but there are important exceptions that AI-generated code hits regularly.
Why AI-Generated Code Is Particularly Vulnerable
AI coding tools frequently generate XSS-prone patterns because they prioritize making things work over making them safe. When you ask an AI to "display user comments" or "render HTML content," it reaches for the fastest solution, which often means bypassing the framework's built-in protections.
1. dangerouslySetInnerHTML in React
React escapes content by default, which is one of its best security features. When you render a variable inside JSX, React automatically converts special characters like `<` and `>` into harmless HTML entities. This prevents most XSS attacks.
But there is one major exception: `dangerouslySetInnerHTML`. This prop tells React to skip its safety measures and render raw HTML directly. AI tools use this whenever they need to display HTML content, like blog posts, rich text editor output, or formatted user content:
// AI-generated code - XSS vulnerable
<div dangerouslySetInnerHTML={{ __html: userComment }} />
If `userComment` contains `<script>alert(document.cookie)</script>`, that script executes in every visitor's browser. The attacker now has their session token and can impersonate them.
**Fix: Sanitize HTML before rendering:**
import DOMPurify from "dompurify";
// Safe - malicious scripts are stripped, but formatting is preserved
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userComment) }} />
DOMPurify removes all potentially dangerous HTML (script tags, event handlers, javascript: URLs) while preserving safe formatting like bold text, links, and headings.
**When do you actually need dangerouslySetInnerHTML?** Only when you need to render HTML that comes from outside your application, like a rich text editor, a markdown parser, or content from a CMS. If you are just displaying plain text, use normal JSX rendering instead.
2. innerHTML Direct Manipulation
Outside of React, AI-generated code often manipulates the DOM directly using `innerHTML`. This has the same problem as `dangerouslySetInnerHTML`: it treats the content as HTML and executes any scripts it contains:
// XSS vulnerable - scripts in userInput will execute
element.innerHTML = userInput;
// Safe - content is treated as plain text, no HTML execution
element.textContent = userInput;
The difference between `innerHTML` and `textContent` is critical. `innerHTML` parses the string as HTML, creating DOM elements and executing scripts. `textContent` treats everything as plain text, so `<script>` appears literally as the text "<script>" on the page.
**When to use which:**
Use `textContent` for any user-generated content that should appear as plain text
Use `innerHTML` only when you need HTML rendering AND the content has been sanitized
Never use `innerHTML` with unsanitized user input
3. document.write()
This legacy method is sometimes suggested by AI for injecting content into the page:
// Never use this with user data
document.write("<p>" + userName + "</p>");
`document.write()` is dangerous because it inserts content directly into the HTML document during parsing. It is also a performance anti-pattern that can cause the entire page to re-render.
**Fix: Use DOM APIs instead:**
const p = document.createElement("p");
p.textContent = userName; // Safe - treated as plain text
document.body.appendChild(p);
4. URL-Based XSS
AI-generated link components often do not validate URLs. This creates an XSS vector through the `javascript:` protocol:
// Allows javascript: protocol - clicking this link executes code
<a href={userProvidedUrl}>Click here</a>
If a user sets their profile URL to `javascript:alert(document.cookie)`, anyone who clicks their link gets their session stolen.
**Fix: Validate the URL protocol:**
function isSafeUrl(url: string): boolean {
try {
const parsed = new URL(url, window.location.origin);
return ["http:", "https:", "mailto:"].includes(parsed.protocol);
} catch {
return false;
}
}
// Only safe protocols are allowed
<a href={isSafeUrl(url) ? url : "#"}>Click here</a>
5. Template Literals in HTML
AI-generated code sometimes builds HTML strings using template literals with user data:
// XSS vulnerable - user data injected into HTML string
const html = `<div class="comment">
<h3>${userName}</h3>
<p>${commentText}</p>
</div>`;
container.innerHTML = html;
If `userName` is `<img src=x onerror="steal(document.cookie)">`, the onerror handler executes when the browser tries to load the broken image.
**Fix:** Either use DOM APIs to create elements, or sanitize the entire HTML string with DOMPurify before setting innerHTML.
Types of XSS Attacks
Understanding the different types of XSS helps you know what to look for:
| Type | How It Works | Example | Persistence |
|------|-------------|---------|-------------|
| **Stored XSS** | Malicious script saved in your database, served to all users who view it | Comment containing a script tag | Permanent until removed |
| **Reflected XSS** | Script in URL parameters rendered on the page without sanitization | `?search=<script>...</script>` | One-time per click |
| **DOM XSS** | Client-side JavaScript processes untrusted data and inserts it into the page | Reading `window.location.hash` into innerHTML | Varies |
**Stored XSS** is the most dangerous because it affects every user who views the infected content. A single malicious comment on a popular page can compromise thousands of users.
**Reflected XSS** requires the attacker to trick the victim into clicking a specially crafted link. It is less severe than stored XSS but still common and dangerous.
**DOM XSS** happens entirely in the browser. The server never sees the malicious input. It occurs when client-side JavaScript reads from an untrusted source (URL hash, postMessage, local storage) and writes it to a dangerous sink (innerHTML, eval, document.write).
The Real Impact of XSS
XSS is not just about annoying popup alerts. A successful XSS attack allows the attacker to:
**Steal session tokens** and impersonate the victim
**Read sensitive data** displayed on the page (bank balances, personal information, private messages)
**Modify the page content** to show fake login forms that capture credentials
**Redirect users** to phishing sites that look identical to your app
**Install keyloggers** that capture everything the user types
**Make API calls** as the victim, performing actions they did not authorize
Content Security Policy: Your Safety Net
Even with careful coding, mistakes happen. Content Security Policy (CSP) headers provide an additional layer of protection by telling the browser which scripts are allowed to run:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';
This policy tells the browser to only execute scripts from your own domain. Even if an attacker injects a script tag, the browser will block it from running.
**Note:** Some platforms like WeWeb require relaxed CSP settings (`unsafe-eval`, `unsafe-inline`) to function, which weakens this protection. If you are using such a platform, your other XSS defenses need to be even stronger.
Prevention Checklist
Never use `dangerouslySetInnerHTML` with unsanitized data
Use DOMPurify for any HTML that needs to be rendered
Prefer `textContent` over `innerHTML` for displaying user content
Validate URL protocols before rendering links (only allow http, https, mailto)
Never use `document.write()` with user data
Set Content-Security-Policy headers to restrict script execution
Enable `HttpOnly` and `Secure` flags on session cookies
Sanitize user input on the server before storing it in the database
Use parameterized templates instead of string concatenation for HTML generation
Test your app by submitting `<script>alert(1)</script>` in every input field
Scan for XSS Vulnerabilities
SimplyScan's free scan checks 3 core categories (Secrets, Frontend, Supabase). Go Pro for all 14 categories including XSS, with 40+ checks that detect `dangerouslySetInnerHTML`, `innerHTML`, `document.write`, unsanitized input patterns, and missing CSP headers.
[Scan your app now](/)
Related Guides
[CSRF Protection and Security Headers](/blog/csrf-security-headers-guide)
[Code Injection Prevention](/blog/code-injection-prevention)
[WeWeb Security Guide](/blog/weweb-security-guide)
[Is Vibe Coding Safe?](/blog/is-vibe-coding-safe)