XSS Prevention
Overview
Implement comprehensive Cross-Site Scripting attack prevention through input sanitization, output encoding, Content Security Policy headers, and secure coding practices.
When to Use
- User-generated content display
- Rich text editors
- Comment systems
- Search functionality
- Dynamic HTML generation
- Template rendering scenarios
XSS Attack Types
| Type | Vector | Defense | |------|--------|---------| | Reflected | URL parameters | Output encoding | | Stored | Database content | Input sanitization | | DOM-based | Client-side JS | Safe DOM APIs | | Mutation | HTML parser quirks | Strict sanitization |
Output Encoding (Node.js)
function encodeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function encodeForAttribute(str) {
return str.replace(/[^\w.-]/g, char =>
`&#x${char.charCodeAt(0).toString(16)};`
);
}
// Usage in templates
app.get('/profile', (req, res) => {
const username = encodeHTML(req.query.name);
res.send(`<h1>Welcome, ${username}</h1>`);
});
DOMPurify Sanitization
import DOMPurify from 'dompurify';
const config = {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
ALLOWED_ATTR: ['href', 'title'],
ALLOW_DATA_ATTR: false
};
function sanitizeHTML(dirty) {
return DOMPurify.sanitize(dirty, config);
}
// React component
function RichContent({ html }) {
return (
<div dangerouslySetInnerHTML={{ __html: sanitizeHTML(html) }} />
);
}
Content Security Policy
// Express middleware
app.use((req, res, next) => {
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.example.com",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'"
].join('; '));
next();
});
Safe DOM APIs
❌ NEVER do any of the following with user-controlled input — these are XSS sinks and there is no safe way to call them with untrusted data:
- Assign it to
element.innerHTML/element.outerHTML - Pass it to
eval() - Pass it to
document.write() - Pass it to
setTimeout/setIntervalas a string - Insert it into an inline event handler (e.g.
onclick="...")
// SAFE — use these instead
element.textContent = userInput; // Escaped automatically
element.setAttribute('data-id', id); // Safe for attributes
document.createTextNode(userInput); // Creates safe text node
The safe patterns above (textContent, attribute escaping via setAttribute,
DOMPurify.sanitize) are the only correct ways to handle user input in the DOM.
URL Validation
function isSafeURL(url) {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
// Usage
const href = isSafeURL(userURL) ? userURL : '#';
Context-Specific Encoding
Different contexts require different encoding approaches:
- HTML Entity Encoding: Safest option for text content
- Attribute Encoding: For HTML attributes
- JavaScript Escaping: For script contexts
- URL Encoding: For URL parameters
- CSS Escaping: For stylesheet contexts
Always encode output by the specific context where data will be rendered.
Additional Implementations
See references/python-sanitization.md for:
- Python bleach library usage
- Flask/Django template escaping
- Server-side validation patterns
See references/nodejs-advanced.md for:
- Complete XSSPrevention class with all methods
- Express middleware (xssProtection)
- React components (SafeText, SafeHTML, SafeLink, useSanitizedInput)
- Helmet CSP configuration
Best Practices
✅ DO:
- Encode output by default
- Use templating engines with auto-escaping
- Implement CSP headers
- Sanitize rich content with allowlists
- Validate URLs with protocol whitelisting
- Use HTTPOnly cookies
- Conduct regular security testing
- Leverage secure frameworks
❌ DON'T:
- Trust user input
- Use unsafe functions (eval, innerHTML)
- Disable security features for convenience
- Rely solely on client-side validation
- Use blocklists instead of allowlists
- Skip context-specific encoding
- Allow arbitrary script execution
Security Checklist
- [ ] Encode all output by context (HTML, attribute, JS)
- [ ] Sanitize HTML with allowlist (not blocklist)
- [ ] Implement strict CSP headers
- [ ] Use HTTPOnly cookies for sessions
- [ ] Validate and sanitize URLs
- [ ] Avoid innerHTML with user content
- [ ] Regular security testing