xss-prevention
About
This skill provides XSS prevention for web applications handling user-generated content through input sanitization, output encoding, and Content Security Policy (CSP). It is designed for use cases like comment systems, rich text editors, and dynamic HTML generation. The implementation includes secure coding practices and libraries like DOMPurify for comprehensive protection.
Documentation
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
/**
* 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
Quick Install
/plugin add https://github.com/aj-geddes/useful-ai-prompts/tree/main/xss-preventionCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
subagent-driven-development
DevelopmentThis skill executes implementation plans by dispatching a fresh subagent for each independent task, with code review between tasks. It enables fast iteration while maintaining quality gates through this review process. Use it when working on mostly independent tasks within the same session to ensure continuous progress with built-in quality checks.
algorithmic-art
MetaThis Claude Skill creates original algorithmic art using p5.js with seeded randomness and interactive parameters. It generates .md files for algorithmic philosophies, plus .html and .js files for interactive generative art implementations. Use it when developers need to create flow fields, particle systems, or other computational art while avoiding copyright issues.
executing-plans
DesignUse the executing-plans skill when you have a complete implementation plan to execute in controlled batches with review checkpoints. It loads and critically reviews the plan, then executes tasks in small batches (default 3 tasks) while reporting progress between each batch for architect review. This ensures systematic implementation with built-in quality control checkpoints.
cost-optimization
OtherThis Claude Skill helps developers optimize cloud costs through resource rightsizing, tagging strategies, and spending analysis. It provides a framework for reducing cloud expenses and implementing cost governance across AWS, Azure, and GCP. Use it when you need to analyze infrastructure costs, right-size resources, or meet budget constraints.
