Skip to content
Back to skills

Xss Prevention

ASecurity

Prevent Cross-Site Scripting (XSS) attacks through input sanitization, output encoding, and Content Security Policy. Use when handling user-generated content in web applications.

  • 327 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added February 7, 2026
developmentjavascriptpythonrustgojavareactnodeexpressdjangoflask

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add aj-geddes/useful-ai-prompts --skill xss-prevention --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Xss Prevention?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Xss Prevention
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aj-geddes-xss-prevention/badge)](https://www.skillsdirectory.com/skills/aj-geddes-xss-prevention)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: xss-prevention
description: Prevent Cross-Site Scripting (XSS) attacks through input sanitization, output encoding, and Content Security Policy. Use when handling user-generated content in web applications.
---

# XSS Prevention

## Overview

Implement comprehensive Cross-Site Scripting (XSS) prevention using input sanitization, output encoding, CSP headers, and secure coding practices.

## When to Use

- User-generated content display
- Rich text editors
- Comment systems
- Search functionality
- Dynamic HTML generation
- Template rendering

## Implementation Examples

### 1. **Node.js XSS Prevention**

```javascript
// xss-prevention.js
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const he = require('he');

const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

class XSSPrevention {
  /**
   * HTML Entity Encoding - Safest for text content
   */
  static encodeHTML(str) {
    return he.encode(str, {
      useNamedReferences: true,
      encodeEverything: false
    });
  }

  /**
   * Sanitize HTML - For rich content
   */
  static sanitizeHTML(dirty) {
    const config = {
      ALLOWED_TAGS: [
        'p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3',
        'ul', 'ol', 'li', 'a', 'img', 'blockquote', 'code'
      ],
      ALLOWED_ATTR: [
        'href', 'src', 'alt', 'title', 'class'
      ],
      ALLOWED_URI_REGEXP: /^(?:https?|mailto):/i,
      KEEP_CONTENT: true,
      RETURN_DOM: false,
      RETURN_DOM_FRAGMENT: false
    };

    return DOMPurify.sanitize(dirty, config);
  }

  /**
   * Strict sanitization - For untrusted HTML
   */
  static sanitizeStrict(dirty) {
    return DOMPurify.sanitize(dirty, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
      ALLOWED_ATTR: [],
      KEEP_CONTENT: true
    });
  }

  /**
   * JavaScript context encoding
   */
  static encodeForJS(str) {
    return str.replace(/[<>"'&]/g, (char) => {
      const escape = {
        '<': '\\x3C',
        '>': '\\x3E',
        '"': '\\x22',
        "'": '\\x27',
        '&': '\\x26'
      };
      return escape[char];
    });
  }

  /**
   * URL parameter encoding
   */
  static encodeURL(str) {
    return encodeURIComponent(str);
  }

  /**
   * Attribute context encoding
   */
  static encodeAttribute(str) {
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#x27;')
      .replace(/\//g, '&#x2F;');
  }

  /**
   * Validate and sanitize URLs
   */
  static sanitizeURL(url) {
    try {
      const parsed = new URL(url);

      // Only allow safe protocols
      if (!['http:', 'https:', 'mailto:'].includes(parsed.protocol)) {
        return '';
      }

      return parsed.href;
    } catch {
      return '';
    }
  }

  /**
   * Strip all HTML tags
   */
  static stripHTML(str) {
    return str.replace(/<[^>]*>/g, '');
  }

  /**
   * React-style JSX escaping
   */
  static escapeForReact(str) {
    return {
      __html: DOMPurify.sanitize(str)
    };
  }
}

// Express middleware
function xssProtection(req, res, next) {
  // Sanitize request body
  if (req.body) {
    req.body = sanitizeObject(req.body);
  }

  // Sanitize query parameters
  if (req.query) {
    req.query = sanitizeObject(req.query);
  }

  next();
}

function sanitizeObject(obj) {
  const sanitized = {};

  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === 'string') {
      sanitized[key] = XSSPrevention.stripHTML(value);
    } else if (typeof value === 'object' && value !== null) {
      sanitized[key] = sanitizeObject(value);
    } else {
      sanitized[key] = value;
    }
  }

  return sanitized;
}

// Express example
const express = require('express');
const app = express();

app.use(express.json());
app.use(xssProtection);

app.post('/api/comments', (req, res) => {
  const { comment } = req.body;

  // Additional sanitization for rich content
  const safeComment = XSSPrevention.sanitizeHTML(comment);

  // Store in database
  // db.comments.insert({ content: safeComment });

  res.json({ comment: safeComment });
});

module.exports = XSSPrevention;
```

### 2. **Python XSS Prevention**

```python
# xss_prevention.py
import html
import bleach
from urllib.parse import urlparse, quote
import re

class XSSPrevention:
    # Allowed HTML tags for rich content
    ALLOWED_TAGS = [
        'p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3',
        'ul', 'ol', 'li', 'a', 'blockquote', 'code'
    ]

    ALLOWED_ATTRIBUTES = {
        'a': ['href', 'title'],
        'img': ['src', 'alt']
    }

    @staticmethod
    def encode_html(text: str) -> str:
        """HTML entity encoding - safest for text content"""
        return html.escape(text, quote=True)

    @staticmethod
    def sanitize_html(dirty_html: str) -> str:
        """Sanitize HTML - for rich content"""
        return bleach.clean(
            dirty_html,
            tags=XSSPrevention.ALLOWED_TAGS,
            attributes=XSSPrevention.ALLOWED_ATTRIBUTES,
            strip=True
        )

    @staticmethod
    def sanitize_strict(dirty_html: str) -> str:
        """Strict sanitization - strip all HTML"""
        return bleach.clean(
            dirty_html,
            tags=[],
            attributes={},
            strip=True
        )

    @staticmethod
    def strip_html(text: str) -> str:
        """Remove all HTML tags"""
        return re.sub(r'<[^>]*>', '', text)

    @staticmethod
    def sanitize_url(url: str) -> str:
        """Validate and sanitize URLs"""
        try:
            parsed = urlparse(url)

            # Only allow safe protocols
            if parsed.scheme not in ['http', 'https', 'mailto']:
                return ''

            return url
        except:
            return ''

    @staticmethod
    def encode_for_javascript(text: str) -> str:
        """Encode for JavaScript context"""
        escape_map = {
            '<': '\\x3C',
            '>': '\\x3E',
            '"': '\\x22',
            "'": '\\x27',
            '&': '\\x26',
            '/': '\\x2F'
        }

        return ''.join(escape_map.get(char, char) for char in text)

    @staticmethod
    def encode_url_param(text: str) -> str:
        """Encode for URL parameters"""
        return quote(text, safe='')

# Flask integration
from flask import Flask, request, jsonify
from functools import wraps

app = Flask(__name__)

def sanitize_input(f):
    """Decorator to sanitize all request inputs"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if request.is_json:
            data = request.get_json()
            request._cached_json = sanitize_dict(data)

        return f(*args, **kwargs)

    return decorated_function

def sanitize_dict(data: dict) -> dict:
    """Recursively sanitize dictionary values"""
    sanitized = {}

    for key, value in data.items():
        if isinstance(value, str):
            sanitized[key] = XSSPrevention.strip_html(value)
        elif isinstance(value, dict):
            sanitized[key] = sanitize_dict(value)
        elif isinstance(value, list):
            sanitized[key] = [
                sanitize_dict(item) if isinstance(item, dict)
                else XSSPrevention.strip_html(item) if isinstance(item, str)
                else item
                for item in value
            ]
        else:
            sanitized[key] = value

    return sanitized

@app.route('/api/comments', methods=['POST'])
@sanitize_input
def create_comment():
    data = request.get_json()
    comment = data.get('comment', '')

    # Additional rich content sanitization
    safe_comment = XSSPrevention.sanitize_html(comment)

    return jsonify({'comment': safe_comment})

# Django template filter
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.filter(name='sanitize_html')
def sanitize_html_filter(value):
    """Django template filter for HTML sanitization"""
    sanitized = XSSPrevention.sanitize_html(value)
    return mark_safe(sanitized)

# Usage in templates:
# {{ user_content|sanitize_html }}
```

### 3. **React XSS Prevention**

```javascript
// XSSSafeComponent.jsx
import React from 'react';
import DOMPurify from 'dompurify';

// Safe text rendering (React automatically escapes)
function SafeText({ text }) {
  return <div>{text}</div>;
}

// Sanitized HTML rendering
function SafeHTML({ html }) {
  const sanitized = DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 'a'],
    ALLOWED_ATTR: ['href']
  });

  return (
    <div
      dangerouslySetInnerHTML={{ __html: sanitized }}
    />
  );
}

// Safe URL attribute
function SafeLink({ href, children }) {
  const safeHref = sanitizeURL(href);

  return (
    <a
      href={safeHref}
      rel="noopener noreferrer"
      target="_blank"
    >
      {children}
    </a>
  );
}

function sanitizeURL(url) {
  try {
    const parsed = new URL(url);

    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return '';
    }

    return parsed.href;
  } catch {
    return '';
  }
}

// Input sanitization hook
function useSanitizedInput(initialValue = '') {
  const [value, setValue] = React.useState(initialValue);

  const handleChange = (e) => {
    const sanitized = DOMPurify.sanitize(e.target.value, {
      ALLOWED_TAGS: [],
      KEEP_CONTENT: true
    });

    setValue(sanitized);
  };

  return [value, handleChange];
}

// Usage
function CommentForm() {
  const [comment, handleCommentChange] = useSanitizedInput();

  const handleSubmit = async (e) => {
    e.preventDefault();

    await fetch('/api/comments', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ comment })
    });
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={comment}
        onChange={handleCommentChange}
        placeholder="Enter comment"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export { SafeText, SafeHTML, SafeLink, useSanitizedInput };
```

### 4. **Content Security Policy**

```javascript
// csp-config.js
const helmet = require('helmet');

function setupCSP(app) {
  app.use(helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],

      // Only allow scripts from trusted sources
      scriptSrc: [
        "'self'",
        "'nonce-RANDOM_NONCE'", // Use dynamic nonces
        "https://cdn.example.com"
      ],

      // Styles
      styleSrc: [
        "'self'",
        "'nonce-RANDOM_NONCE'",
        "https://fonts.googleapis.com"
      ],

      // No inline styles/scripts
      objectSrc: ["'none'"],
      baseUri: ["'self'"],

      // Report violations
      reportUri: ['/api/csp-violations']
    }
  }));

  // CSP violation reporter
  app.post('/api/csp-violations', (req, res) => {
    console.error('CSP Violation:', req.body);
    res.status(204).end();
  });
}

// Generate nonce for inline scripts
function generateNonce() {
  return require('crypto').randomBytes(16).toString('base64');
}

// Express middleware to add nonce
app.use((req, res, next) => {
  res.locals.nonce = generateNonce();
  next();
});

// In templates: <script nonce="<%= nonce %>">
```

## Best Practices

### ✅ DO
- Encode output by default
- Use templating engines
- Implement CSP headers
- Sanitize rich content
- Validate URLs
- Use HTTPOnly cookies
- Regular security testing
- Use secure frameworks

### ❌ DON'T
- Trust user input
- Use innerHTML directly
- Skip output encoding
- Allow inline scripts
- Use eval()
- Mix contexts (HTML/JS)

## XSS Types

- **Reflected**: Immediate response
- **Stored**: Persisted in database
- **DOM-based**: Client-side manipulation
- **Mutation-based**: Parser differences

## Context-Specific Encoding

- **HTML Content**: HTML entity encoding
- **HTML Attribute**: Attribute encoding
- **JavaScript**: JavaScript escaping
- **URL**: URL encoding
- **CSS**: CSS escaping

## Resources

- [OWASP XSS Prevention](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html)
- [DOMPurify](https://github.com/cure53/DOMPurify)
- [Content Security Policy](https://content-security-policy.com/)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.