Back to Blog
Security9 min

XSS Prevention: A Complete Guide to Cross-Site Scripting Protection

Learn how to prevent XSS attacks with practical examples and best practices for modern web applications.

By Security TeamFebruary 22, 2026

Cross-Site Scripting (XSS) allows attackers to inject malicious JavaScript into web pages viewed by other users. It's one of the most common web vulnerabilities, accounting for nearly 40% of all cyberattacks.

Types of XSS Attacks

1. Reflected XSS

Malicious script is reflected off a web server, typically via URL parameters.

// Vulnerable search page
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>Results for: ${query}</h1>`);
});

// Attack URL:
// /search?q=<script>alert(document.cookie)</script>

2. Stored XSS

Malicious script is permanently stored on the server (database, comment system, etc.).

// Vulnerable comment system
app.post('/comment', async (req, res) => {
  const comment = req.body.comment;
  await db.insert({ text: comment });  // Stored!
  res.redirect('/comments');
});

// Later, when displaying comments:
comments.forEach(c => {
  html += `<div>${c.text}</div>`;  // XSS executed!
});

3. DOM-Based XSS

Vulnerability exists in client-side code, not server-side.

// Vulnerable client-side code
const name = new URLSearchParams(location.search).get('name');
document.getElementById('welcome').innerHTML = `Hello ${name}!`;

// Attack URL:
// /?name=<img src=x onerror=alert(document.cookie)>

Common XSS Attack Vectors

Script Tags

<script>alert('XSS')</script>

Event Handlers

<img src=x onerror=alert('XSS')>
<body onload=alert('XSS')>
<svg/onload=alert('XSS')>

JavaScript URIs

<a href="javascript:alert('XSS')">Click</a>
<iframe src="javascript:alert('XSS')"></iframe>

Data URIs

<object data="data:text/html,<script>alert('XSS')</script>"></object>

Prevention Techniques

1. Output Encoding (Primary Defense)

Always encode user input when displaying it.

HTML Context:

// ❌ Vulnerable
res.send(`<div>${userInput}</div>`);

// ✅ Safe: HTML entity encoding
function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;')
    .replace(//g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#x27;');
}
res.send(`<div>${escapeHtml(userInput)}</div>`);

JavaScript Context:

// ❌ Vulnerable
<script>const name = "${userInput}";</script>

// ✅ Safe: JSON.stringify
<script>const name = ${JSON.stringify(userInput)};</script>

URL Context:

// ❌ Vulnerable
<a href="/search?q=${query}">Search</a>

// ✅ Safe: URL encoding
<a href="/search?q=${encodeURIComponent(query)}">Search</a>

2. Use Template Engines with Auto-Escaping

React (auto-escapes by default):

// ✅ Safe: React escapes automatically
function Welcome({ name }) {
  return <h1>Hello {name}!</h1>;
}

// ⚠️ Dangerous: Only use for trusted HTML
function DangerousHTML({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Vue.js:

<!-- ✅ Safe: Auto-escaped -->
<div>{{ userInput }}</div>

<!-- ⚠️ Dangerous: Raw HTML -->
<div v-html="userInput"></div>

EJS:

<!-- ✅ Safe: Auto-escaped -->
<div><%= userInput %></div>

<!-- ⚠️ Dangerous: Raw HTML -->
<div><%- userInput %></div>

3. Content Security Policy (CSP)

Whitelist allowed script sources to prevent inline script execution.

// Express middleware
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none';"
  );
  next();
});

Strict CSP (best practice):

Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

4. Sanitize User HTML (When Needed)

If you must allow user HTML (rich text editors), use a sanitizer library.

DOMPurify (client-side):

import DOMPurify from 'dompurify';

const dirty = '<img src=x onerror=alert("XSS")>';
const clean = DOMPurify.sanitize(dirty);
// Result: <img src="x">

sanitize-html (Node.js):

const sanitizeHtml = require('sanitize-html');

const dirty = '<script>alert("XSS")</script><p>Safe content</p>';
const clean = sanitizeHtml(dirty, {
  allowedTags: ['p', 'b', 'i', 'em', 'strong', 'a'],
  allowedAttributes: {
    'a': ['href']
  }
});
// Result: <p>Safe content</p>

5. Input Validation

While not a primary XSS defense, validation adds defense-in-depth.

// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  throw new Error('Invalid email');
}

// Allowlist validation for specific inputs
const allowedColors = ['red', 'blue', 'green'];
if (!allowedColors.includes(color)) {
  throw new Error('Invalid color');
}

6. HttpOnly Cookies

Prevent JavaScript access to session cookies.

res.cookie('sessionId', value, {
  httpOnly: true,  // Not accessible via JavaScript
  secure: true,    // HTTPS only
  sameSite: 'strict'  // CSRF protection
});

Framework-Specific Protection

React

// ✅ Safe by default
<div>{userInput}</div>

// ❌ Only when you REALLY need raw HTML
<div dangerouslySetInnerHTML={{ __html: sanitizedHtml }} />

Angular

<!-- ✅ Safe: Auto-sanitized -->
<div>{{ userInput }}</div>

<!-- ✅ Safe: DomSanitizer for trusted HTML -->
<div [innerHTML]="sanitizer.bypassSecurityTrustHtml(trustedHtml)"></div>

Express.js

// Use templating engines with auto-escape
app.set('view engine', 'ejs');

// Or manually escape
const escape = require('escape-html');
res.send(`<div>${escape(userInput)}</div>`);

Testing for XSS

Manual Testing

Test with common XSS payloads:

<script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
<svg/onload=alert('XSS')>
javascript:alert('XSS')
<iframe src="javascript:alert('XSS')"></iframe>

Automated Scanning

  • OWASP ZAP: Free security scanner
  • Burp Suite: Professional web vulnerability scanner
  • ScanMyCode.dev: AI-powered code audit checks for XSS vulnerabilities

Real-World XSS Examples

Twitter 2010

Reflected XSS in search allowed attackers to create self-retweeting worms.

TweetDeck 2014

Stored XSS in tweets caused widespread auto-retweets and account compromises.

British Airways 2018

XSS attack on payment page led to theft of 380,000 payment cards. Fine: £183M.

XSS Prevention Checklist

  • ✅ Encode ALL user input on output
  • ✅ Use templating engines with auto-escaping
  • ✅ Implement Content Security Policy
  • ✅ Never use eval() or innerHTML with user input
  • ✅ Sanitize HTML if rich text is needed
  • ✅ Use HttpOnly cookies for sessions
  • ✅ Validate input (defense-in-depth)
  • ✅ Regular security testing and audits
  • ✅ Keep frameworks and dependencies updated

Conclusion

XSS is preventable with proper output encoding and security headers. Don't trust user input, ever. Treat all data as untrusted until properly encoded for the context in which it's used.

Want to ensure your code is free from XSS vulnerabilities? Get a security audit and receive a comprehensive XSS analysis within 24 hours.

XSSsecurityweb securitysanitization

Ready to improve your code?

Get an AI-powered code audit with actionable recommendations. Results in 24 hours.

Start Your Audit