Claude Code Plugins

Community-maintained marketplace

Feedback

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

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

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

// 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

# 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

// 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

// 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