// 1. DANGEROUS: Never use innerHTML with user input
const userInput = '<img src=x onerror="alert('XSS')">';
// WRONG - vulnerable to XSS
document.getElementById('output').innerHTML = userInput;
// RIGHT - safe, treats as text
document.getElementById('output').textContent = userInput;
// 2. Safe DOM manipulation
function displayUserComment(comment) {
const commentEl = document.createElement('div');
commentEl.className = 'comment';
// Create text nodes (safe)
const authorEl = document.createElement('strong');
authorEl.textContent = comment.author; // Safe
const textEl = document.createElement('p');
textEl.textContent = comment.text; // Safe
commentEl.appendChild(authorEl);
commentEl.appendChild(textEl);
document.getElementById('comments').appendChild(commentEl);
}
// 3. Sanitize HTML with DOMPurify
import DOMPurify from 'dompurify';
function displayRichContent(html) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
ALLOWED_ATTR: ['href'],
});
document.getElementById('content').innerHTML = clean;
}
// Usage
const userHTML = '<p>Hello <script>alert("XSS")</script></p>';
displayRichContent(userHTML); // Script tag removed
// 4. Escape user input for HTML context
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// Usage
const userText = '<script>alert("XSS")</script>';
const safe = escapeHtml(userText);
// Result: <script>alert("XSS")</script>
// 5. Validate and sanitize URLs
function isSafeUrl(url) {
try {
const parsed = new URL(url);
// Only allow http/https
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
function createSafeLink(url, text) {
if (!isSafeUrl(url)) {
console.warn('Unsafe URL blocked:', url);
return document.createTextNode(text);
}
const link = document.createElement('a');
link.href = url;
link.textContent = text;
link.rel = 'noopener noreferrer'; // Security: prevent window.opener access
return link;
}
// 6. CSRF protection with tokens
async function submitForm(data) {
// Get CSRF token from meta tag
const token = document.querySelector('meta[name="csrf-token"]')?.content;
if (!token) {
throw new Error('CSRF token not found');
}
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token,
},
credentials: 'same-origin', // Include cookies
body: JSON.stringify(data),
});
return response.json();
}
// HTML: <meta name="csrf-token" content="<%= csrf_token %>">
// 7. Input validation
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
function validateUsername(username) {
// Only alphanumeric and underscore, 3-20 characters
const regex = /^[a-zA-Z0-9_]{3,20}$/;
return regex.test(username);
}
function sanitizeInput(input, maxLength = 100) {
// Remove control characters and trim
return input
.replace(/[\x00-\x1F\x7F]/g, '')
.trim()
.slice(0, maxLength);
}
// 8. Prevent clickjacking
// Server-side header: X-Frame-Options: DENY
// Or use CSP: Content-Security-Policy: frame-ancestors 'none'
// Client-side check
if (window.top !== window.self) {
// Page is in an iframe
document.body.innerHTML = 'This page cannot be displayed in an iframe';
}
// 9. Secure cookie handling
// Server sets HTTP-only cookies
// Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Strict
// Client can't access HTTP-only cookies
// But can read non-HTTP-only cookies
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [key, value] = cookie.trim().split('=');
if (key === name) {
return decodeURIComponent(value);
}
}
return null;
}
// 10. Content Security Policy (CSP)
/*
Server-side header:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
*/
// 11. Subresource Integrity (SRI)
/*
<script
src="https://cdn.example.com/library.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
*/
// 12. Rate limiting on client
class RateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = [];
}
canMakeRequest() {
const now = Date.now();
this.requests = this.requests.filter(time => now - time < this.windowMs);
if (this.requests.length < this.maxRequests) {
this.requests.push(now);
return true;
}
return false;
}
}
// Usage
const limiter = new RateLimiter(5, 60000); // 5 requests per minute
async function makeApiCall() {
if (!limiter.canMakeRequest()) {
alert('Too many requests. Please wait.');
return;
}
await fetch('/api/data');
}