High CWE-79 OWASP A03:2021

XSS (Cross-Site Scripting) in Vibe Coded Apps

How to find and fix script injection vulnerabilities in your web application

Quick Answer

XSS lets attackers inject malicious scripts into your web pages. Scripts steal cookies, capture keystrokes, and hijack sessions. Classified under OWASP A03:2021. AI tools cause this with dangerouslySetInnerHTML and v-html.

A03:2021
OWASP Category
CWE-79
3 Types
Stored, Reflected, DOM
High
Severity

Source: OWASP Top 10 (2021)

What is Cross-Site Scripting (XSS)?

Cross-Site Scripting (XSS) is a vulnerability where attackers inject malicious JavaScript into web pages viewed by other users. When a user visits the page, the script runs in their browser and can steal cookies, capture keystrokes, or redirect to phishing sites.

There are three types of XSS. Stored XSS saves the malicious script in your database (comments, profiles, posts) and serves it to everyone. Reflected XSS bounces the script off your server through URL parameters. DOM-based XSS happens entirely in the browser when JavaScript processes untrusted data.

According to OWASP, injection attacks (including XSS) rank #3 in the Top 10 Web Application Security Risks. The PortSwigger XSS Guide provides comprehensive exploitation techniques and prevention strategies. Vibe coders are especially vulnerable when building features that display user content.

How do AI tools cause XSS?

AI tools cause XSS by generating code that bypasses framework escaping with dangerouslySetInnerHTML, v-html, or @html. When you ask to "render HTML content" or "display rich text," AI reaches for these escape hatches because they work, not because they're safe.

Common AI-generated vulnerable patterns

When you ask AI tools to display dynamic HTML, they generate code like this:

// React - AI generates this for "render user content as HTML"
function Comment({ content }) {
  return <div dangerouslySetInnerHTML={{ __html: content }} />
}

// Vue - AI generates this for "display markdown"
<template>
  <div v-html="userContent"></div>
</template>

// Svelte - AI generates this for "show formatted text"
{@html userInput}

These patterns work for rendering HTML, which is why AI suggests them. But if content, userContent, or userInput comes from users, attackers can inject scripts.

Why this happens: When you prompt "display user-generated HTML" or "render markdown," the AI interprets this literally. It provides a working solution without considering that "user-generated" might include malicious scripts. The AI optimizes for functionality, not security.

All major AI coding tools (Cursor, Claude Code, Bolt, v0, and Copilot) can generate these patterns. Whether you're vibe coding a blog, comment system, or rich text editor, watch for these dangerous HTML rendering patterns in your AI-generated code.

What could happen if I have XSS?

XSS can result in session hijacking, credential theft, malware distribution, and complete account takeover for every user who visits the affected page.

  • Session hijacking: Attackers steal session cookies and impersonate users, accessing their accounts without passwords
  • Credential theft: Injected scripts capture login forms, sending usernames and passwords to attacker servers
  • Malware distribution: XSS redirects users to malicious downloads or exploit kits
  • Defacement: Attackers modify page content, displaying fake information or damaging your brand
  • Keylogging: Scripts record everything users type, including credit cards, messages, and sensitive data

How do I detect XSS vulnerabilities?

Detect XSS vulnerabilities by searching for dangerouslySetInnerHTML, v-html, @html, innerHTML, and document.write in your codebase.

Patterns to search for
# React
dangerouslySetInnerHTML
__html

# Vue
v-html

# Svelte
{@html

# Angular
[innerHTML]
bypassSecurityTrust

# Vanilla JS
innerHTML =
outerHTML =
document.write(
insertAdjacentHTML

# jQuery
.html(
.append( (with user input)

# URL-based XSS
href={userInput}
src={userInput}
javascript:

Test for XSS: Try entering <script>alert('XSS')</script> or <img src=x onerror=alert('XSS')> in input fields. If an alert appears, you have an XSS vulnerability.

Want to find XSS vulnerabilities automatically?

Try Vibeship Scanner

How do I fix XSS vulnerabilities?

Fix XSS by removing dangerous HTML rendering patterns and letting your framework escape content, or sanitize with DOMPurify if you must render HTML.

AI Fix Prompt

Copy this prompt into Cursor, Claude Code, or Bolt to automatically fix XSS vulnerabilities:

Copy-paste this prompt
Fix all XSS (Cross-Site Scripting) vulnerabilities in my codebase. ## What to look for Search for these dangerous patterns: 1. React: - dangerouslySetInnerHTML={{ __html: userInput }} - Any use of dangerouslySetInnerHTML with untrusted data 2. Vue: - v-html="userInput" - v-html with any data that could come from users 3. Svelte: - {@html userInput} - @html with any user-controlled content 4. Vanilla JavaScript: - element.innerHTML = userInput - document.write(userInput) - insertAdjacentHTML with user data 5. URL-based XSS: - href={userInput} (could be javascript:) - src={userInput} - Dynamic URLs without validation ## How to fix Option 1: Remove dangerous patterns (preferred) ```javascript // Before (vulnerable) <div dangerouslySetInnerHTML={{ __html: userComment }} /> // After (safe - let React escape it) <div>{userComment}</div> ``` Option 2: Sanitize if HTML rendering is required ```javascript import DOMPurify from 'dompurify'; // Before (vulnerable) <div dangerouslySetInnerHTML={{ __html: userContent }} /> // After (sanitized) <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} /> ``` Option 3: Use safe markdown libraries ```javascript // Instead of rendering raw HTML for markdown import ReactMarkdown from 'react-markdown'; // Safe markdown rendering (no raw HTML by default) <ReactMarkdown>{userMarkdown}</ReactMarkdown> ``` ## URL validation ```javascript // Before (vulnerable to javascript: URLs) <a href={userUrl}>Link</a> // After (validated) const safeUrl = userUrl.startsWith('http://') || userUrl.startsWith('https://') ? userUrl : '#'; <a href={safeUrl}>Link</a> ``` ## After fixing 1. Search for remaining patterns: dangerouslySetInnerHTML, v-html, @html, innerHTML 2. Verify all user input is either escaped or sanitized 3. Test with: <script>alert('XSS')</script> and <img src=x onerror=alert('XSS')> 4. Add Content Security Policy headers as defense in depth 5. List all files modified with before/after examples Please proceed systematically through my codebase.

Manual Fix

The safest fix is to avoid rendering raw HTML. Let your framework escape content.

VULNERABLE
// React - Renders raw HTML, XSS possible
function Comment({ content }) {
  return (
    <div dangerouslySetInnerHTML={{ __html: content }} />
  )
}

// Vue - Same problem
<div v-html="userContent"></div>

// Svelte - Same problem
{@html userInput}
SECURE
// React - Let React escape content
function Comment({ content }) {
  return (
    <div>{content}</div>
  )
}

// Vue - Use text interpolation
<div>{{ userContent }}</div>

// Svelte - Use normal interpolation
{userInput}

If you must render HTML (like a rich text editor), sanitize it with DOMPurify, the industry-standard sanitization library:

import DOMPurify from 'dompurify';

// Sanitize before rendering
const cleanHTML = DOMPurify.sanitize(untrustedHTML);

// React
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} />

// Vue
<div v-html="cleanHTML"></div>

// Svelte
{@html cleanHTML}

Frequently asked questions

Does React protect me from XSS automatically?

Mostly yes. React escapes all values embedded in JSX by default, which prevents most XSS attacks. However, you can still create vulnerabilities by using dangerouslySetInnerHTML, passing user input to href attributes with javascript: URLs, or using refs to directly manipulate the DOM. The protection only works when you let React handle rendering.

What is the difference between stored and reflected XSS?

Stored XSS saves the malicious script in your database (like a comment or profile field) and serves it to every user who views that content. Reflected XSS bounces the script off your server via URL parameters or form submissions, typically through phishing links. Stored XSS is more dangerous because it affects all users automatically.

Can XSS steal passwords and credit cards?

Yes. XSS scripts run with full access to the page, so they can read form inputs as users type, steal session cookies, redirect users to fake login pages, modify page content to show fake payment forms, and send all captured data to attacker-controlled servers. This is why XSS is considered high severity.

Why do AI coding tools generate XSS vulnerabilities?

When vibe coding, AI tools generate XSS vulnerabilities whenever asked to render dynamic HTML. Requests like "display user-generated HTML" or "render markdown" lead to dangerouslySetInnerHTML in React, v-html in Vue, or @html in Svelte. The AI provides what works for vibe coders without considering malicious content.

Is sanitizing HTML enough to prevent XSS?

Sanitization helps but is not foolproof. Libraries like DOMPurify are well-tested, but sanitizers can have bypasses, and you must configure them correctly. The safest approach is to avoid rendering raw HTML entirely. If you must render HTML, use a trusted sanitizer AND set a Content Security Policy as defense in depth.

Related content

Scan your code for XSS vulnerabilities

Check your codebase for dangerous HTML rendering patterns and other security issues.

Try Vibeship Scanner