๐งช Interactive Security Demonstrations with Code
HTML Injection Prevention
๐ Understanding the Vulnerability
HTML injection occurs when untrusted data is inserted into the DOM without proper sanitization. The browser interprets this data as code, executing any embedded scripts.
๐ก๏ธ Trusted Types Defense Mechanisms:
- โข TrustedHTML Type: Only accepts sanitized HTML through policies
- โข Default Policy: Fallback sanitization for legacy code
- โข CSP Integration: Blocks violations via
require-trusted-types-for
- โข Sink Protection: Guards innerHTML, outerHTML, insertAdjacentHTML, and more
๐ Learn More: MDN Trusted Types Documentation | Web.dev Guide
๐ป Implementation Code
// 1. Create a sanitization policy for HTML content
const htmlSanitizationPolicy = trustedTypes.createPolicy('html-sanitizer', {
createHTML: (dirty) => {
// Remove dangerous elements and attributes
const cleaned = dirty
// Remove script tags
.replace(/
โข ">
Sanitized Output:
Dynamic Script Security
๐ Script Injection Vectors
Attackers exploit dynamic script creation to execute arbitrary code. Common targets include
eval()
, Function()
,
setTimeout/setInterval
with strings, and dynamic script elements.
๐ก๏ธ TrustedScript & TrustedScriptURL:
- โข TrustedScript: For inline script content and eval-like functions
- โข TrustedScriptURL: For external script sources and worker URLs
- โข Policy Validation: Scripts must pass through createScript/createScriptURL
- โข Import Maps: Protection extends to ES6 module imports
๐ป Implementation Code
// Create policies for script content and URLs
const scriptPolicy = trustedTypes.createPolicy('script-loader', {
createScript: (script) => {
// Validate script content
if (script.includes('eval') || script.includes('Function(')) {
throw new Error('Dangerous functions not allowed');
}
return script;
},
createScriptURL: (url) => {
// Whitelist trusted domains
const trustedDomains = [
'https://cdn.jsdelivr.net',
'https://unpkg.com',
'https://cdnjs.cloudflare.com',
window.location.origin
];
const urlObj = new URL(url, window.location.origin);
if (!trustedDomains.some(domain => urlObj.href.startsWith(domain))) {
throw new Error(`Untrusted script source: ${url}`);
}
return url;
}
});
// Safe dynamic script loading
function loadScriptSafely(scriptUrl) {
const script = document.createElement('script');
// Use the policy to validate the URL
script.src = scriptPolicy.createScriptURL(scriptUrl);
script.onerror = () => {
console.error('Script failed to load:', scriptUrl);
};
script.onload = () => {
console.log('Script loaded successfully:', scriptUrl);
};
document.head.appendChild(script);
}
// Safe inline script execution
function executeInlineScript(code) {
const script = document.createElement('script');
// Use the policy to validate the script content
script.textContent = scriptPolicy.createScript(code);
document.body.appendChild(script);
document.body.removeChild(script); // Clean up
}
// Example usage
loadScriptSafely('https://cdn.jsdelivr.net/npm/lodash@4/lodash.min.js');
executeInlineScript('console.log("Safe script execution");');
// โ UNSAFE - Using eval (blocked by Trusted Types)
function unsafeEval(userCode) {
eval(userCode); // Throws TypeError with Trusted Types
}
// โ UNSAFE - Using Function constructor
function unsafeFunction(userCode) {
new Function(userCode)(); // Also blocked
}
// โ UNSAFE - setTimeout with string
function unsafeTimeout(userCode) {
setTimeout(userCode, 0); // Blocked
}
// โ
SAFE - Use JSON.parse for data
function safeJSONParse(jsonString) {
try {
const data = JSON.parse(jsonString);
return data;
} catch (e) {
console.error('Invalid JSON:', e);
}
}
// โ
SAFE - Use Function references
function safeTimeout(callback) {
setTimeout(callback, 0); // Pass function, not string
}
// โ
SAFE - Use Web Workers for isolation
function safeWorker(code) {
const blob = new Blob([code], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
return worker;
}
Script Execution Log:
Event Handler Safety
๐ Event Handler Attack Surface
Inline event handlers are a major XSS vector. Attributes like onclick
,
onload
, onerror
, and
over 70 other event handlers can execute JavaScript directly from HTML.
๐ก๏ธ Safe Event Handling Patterns:
- โข addEventListener: Programmatic event binding (preferred)
- โข Event Delegation: Single listener for multiple elements
- โข Data Attributes: Store data separately from handlers
- โข CSP unsafe-inline: Block all inline handlers when possible
๐ป Implementation Code
// โ
SAFE: Using addEventListener
function createSafeButton(text, message) {
const button = document.createElement('button');
button.textContent = text;
button.className = 'btn btn-primary';
// Safe event handler attachment
button.addEventListener('click', function(event) {
// Access data from closure or data attributes
console.log('Button clicked:', message);
alert(message);
});
return button;
}
// โ
SAFE: Multiple event listeners
function attachMultipleHandlers(element) {
element.addEventListener('mouseenter', () => {
element.classList.add('hover');
});
element.addEventListener('mouseleave', () => {
element.classList.remove('hover');
});
element.addEventListener('click', (e) => {
e.preventDefault();
handleClick(element);
});
}
// โ UNSAFE: Never use inline handlers
// element.innerHTML = ``;
// โ
SAFE: Create elements programmatically
const container = document.getElementById('buttonContainer');
const safeBtn = createSafeButton('Click Me', 'Hello, World!');
container.appendChild(safeBtn);
// Event Delegation Pattern - Single listener for multiple elements
class ButtonManager {
constructor(container) {
this.container = container;
this.buttons = new Map();
// Single event listener for all buttons
this.container.addEventListener('click', this.handleClick.bind(this));
}
handleClick(event) {
// Check if clicked element is a button
const button = event.target.closest('[data-action]');
if (!button) return;
const action = button.dataset.action;
const id = button.dataset.id;
// Execute appropriate handler
switch(action) {
case 'delete':
this.handleDelete(id);
break;
case 'edit':
this.handleEdit(id);
break;
case 'view':
this.handleView(id);
break;
}
}
addButton(id, action, text) {
const button = document.createElement('button');
button.textContent = text;
button.dataset.action = action;
button.dataset.id = id;
button.className = 'delegated-btn';
this.container.appendChild(button);
this.buttons.set(id, button);
}
handleDelete(id) {
console.log('Deleting:', id);
const button = this.buttons.get(id);
if (button) {
button.remove();
this.buttons.delete(id);
}
}
handleEdit(id) {
console.log('Editing:', id);
}
handleView(id) {
console.log('Viewing:', id);
}
}
// Usage
const manager = new ButtonManager(document.getElementById('container'));
manager.addButton('1', 'delete', 'Delete Item 1');
manager.addButton('2', 'edit', 'Edit Item 2');
manager.addButton('3', 'view', 'View Item 3');
// Using data attributes to store information safely
function createInteractiveElement(config) {
const element = document.createElement('div');
element.className = 'interactive-card';
// Store data in data attributes (automatically escaped)
element.dataset.userId = config.userId;
element.dataset.action = config.action;
element.dataset.metadata = JSON.stringify(config.metadata);
// Safe HTML content
element.innerHTML = `
${escapeHtml(config.title)}
${escapeHtml(config.description)}
`;
// Attach event handler that reads from data attributes
const button = element.querySelector('.action-btn');
button.addEventListener('click', function() {
const userId = element.dataset.userId;
const action = element.dataset.action;
const metadata = JSON.parse(element.dataset.metadata);
performAction(userId, action, metadata);
});
return element;
}
// HTML escaping function
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Action handler
function performAction(userId, action, metadata) {
console.log('Performing action:', {
userId,
action,
metadata
});
// Send to server or handle locally
fetch('/api/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId, action, metadata })
});
}
// Usage
const card = createInteractiveElement({
userId: '123',
action: 'update',
title: 'User Profile',
description: 'Click to update profile',
metadata: { timestamp: Date.now(), source: 'web' }
});
document.getElementById('container').appendChild(card);
Safely Created Buttons:
URL Injection Protection
๐ Dangerous URL Schemes
URLs can execute code through various schemes: javascript:
,
data:
, vbscript:
(IE),
and even jar:
protocols can be exploited.
๐ก๏ธ URL Validation Strategies:
- โข Protocol Whitelist: Only allow http://, https://, mailto:
- โข URL Constructor: Parse and validate URL structure
- โข Same-Origin Checks: Restrict to same domain when needed
- โข Sanitize Parameters: Clean query strings and fragments
๐ป Implementation Code
// Comprehensive URL validation
class URLValidator {
constructor() {
// Allowed protocols
this.allowedProtocols = ['http:', 'https:', 'mailto:'];
// Blocked patterns
this.blockedPatterns = [
/javascript:/i,
/data:(?!image\/)/i, // Allow data: URLs only for images
/vbscript:/i,
/file:/i,
/jar:/i
];
}
validate(url) {
// Check for blocked patterns first
for (const pattern of this.blockedPatterns) {
if (pattern.test(url)) {
throw new Error(`Blocked URL pattern: ${url}`);
}
}
try {
// Use URL constructor for parsing
const urlObj = new URL(url, window.location.origin);
// Check protocol
if (!this.allowedProtocols.includes(urlObj.protocol)) {
throw new Error(`Invalid protocol: ${urlObj.protocol}`);
}
// Additional checks
this.checkForOpenRedirect(urlObj);
this.checkForIDN(urlObj);
return urlObj.href;
} catch (e) {
console.error('URL validation failed:', e);
return null;
}
}
checkForOpenRedirect(urlObj) {
// Check for common open redirect patterns
const params = urlObj.searchParams;
const redirectParams = ['url', 'redirect', 'return', 'next', 'goto'];
for (const param of redirectParams) {
const value = params.get(param);
if (value && !this.isSameOrigin(value)) {
console.warn('Potential open redirect:', value);
}
}
}
checkForIDN(urlObj) {
// Check for IDN homograph attacks
if (urlObj.hostname.includes('xn--')) {
console.warn('IDN domain detected:', urlObj.hostname);
}
}
isSameOrigin(url) {
try {
const urlObj = new URL(url, window.location.origin);
return urlObj.origin === window.location.origin;
} catch {
return false;
}
}
}
// Usage
const validator = new URLValidator();
const safeUrl = validator.validate(userProvidedUrl);
// URL Sanitization for different contexts
class URLSanitizer {
// For anchor tags
sanitizeForHref(url) {
// Remove javascript: and data: protocols
if (/^(javascript|data|vbscript):/i.test(url)) {
return '#blocked';
}
try {
const urlObj = new URL(url, window.location.origin);
// For external links, add rel="noopener noreferrer"
if (urlObj.origin !== window.location.origin) {
return {
href: urlObj.href,
rel: 'noopener noreferrer',
target: '_blank'
};
}
return { href: urlObj.href };
} catch {
return { href: '#invalid' };
}
}
// For image sources
sanitizeForImg(url) {
// Allow data URLs for images
if (/^data:image\//i.test(url)) {
return url;
}
// Block other data URLs
if (/^data:/i.test(url)) {
return '/placeholder.png';
}
// Validate other URLs
try {
const urlObj = new URL(url, window.location.origin);
if (urlObj.protocol === 'https:' || urlObj.protocol === 'http:') {
return urlObj.href;
}
} catch {
return '/placeholder.png';
}
return '/placeholder.png';
}
// For iframe sources
sanitizeForIframe(url) {
// Very strict - only allow same-origin or trusted domains
const trustedDomains = [
'https://www.youtube.com',
'https://player.vimeo.com'
];
try {
const urlObj = new URL(url);
// Check if same-origin
if (urlObj.origin === window.location.origin) {
return url;
}
// Check trusted domains
if (trustedDomains.some(domain => url.startsWith(domain))) {
return url;
}
} catch {
return 'about:blank';
}
return 'about:blank';
}
}
// Usage
const sanitizer = new URLSanitizer();
// Create safe link
function createSafeLink(url, text) {
const a = document.createElement('a');
const sanitized = sanitizer.sanitizeForHref(url);
if (typeof sanitized === 'object') {
Object.assign(a, sanitized);
} else {
a.href = sanitized;
}
a.textContent = text;
return a;
}
// Safe navigation and redirection
class SafeNavigator {
// Safe window.location assignment
navigateTo(url) {
const validated = this.validateURL(url);
if (validated) {
window.location.href = validated;
} else {
console.error('Invalid navigation URL:', url);
this.handleInvalidNavigation();
}
}
// Safe window.open
openWindow(url, target = '_blank') {
const validated = this.validateURL(url);
if (validated) {
const newWindow = window.open(validated, target, 'noopener,noreferrer');
return newWindow;
}
return null;
}
// Safe form action
setFormAction(form, url) {
const validated = this.validateURL(url);
if (validated) {
form.action = validated;
// Add CSRF token if needed
this.addCSRFToken(form);
}
}
// Validate and sanitize URL
validateURL(url) {
try {
const urlObj = new URL(url, window.location.origin);
// Block dangerous protocols
if (!['http:', 'https:'].includes(urlObj.protocol)) {
return null;
}
// For production: implement additional checks
// - Domain whitelist
// - Path validation
// - Parameter sanitization
return urlObj.href;
} catch {
return null;
}
}
// Handle invalid navigation attempts
handleInvalidNavigation() {
// Log security event
console.error('Blocked navigation attempt');
// Show user-friendly error
alert('The link you clicked appears to be invalid.');
// Optionally report to server
this.reportSecurityEvent('invalid_navigation');
}
// Add CSRF protection
addCSRFToken(form) {
const token = document.querySelector('meta[name="csrf-token"]')?.content;
if (token) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'csrf_token';
input.value = token;
form.appendChild(input);
}
}
// Report security events
reportSecurityEvent(event) {
fetch('/api/security/report', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event,
timestamp: Date.now(),
url: window.location.href
})
});
}
}
// Usage
const navigator = new SafeNavigator();
// Safe navigation
document.getElementById('safeLink').addEventListener('click', (e) => {
e.preventDefault();
navigator.navigateTo(e.target.href);
});
// Safe form submission
const form = document.getElementById('myForm');
navigator.setFormAction(form, '/api/submit');
Validated Links:
๐ Security Console
Real-time security events and policy violations:
๐ก๏ธ XSS Prevention Strategies
Input Validation
- โข Validate all user input on both client and server
- โข Use allowlists for expected input patterns
- โข Reject or sanitize suspicious content
- โข Implement proper length restrictions
Output Encoding
- โข HTML entity encoding for HTML contexts
- โข JavaScript encoding for script contexts
- โข URL encoding for URL parameters
- โข CSS encoding for style contexts
Content Security Policy
- โข Implement strict CSP headers
- โข Use nonces or hashes for inline scripts
- โข Restrict script sources to trusted domains
- โข Enable Trusted Types enforcement
Framework Security
- โข Use framework's built-in XSS protection
- โข Avoid dangerous APIs (innerHTML, eval)
- โข Keep frameworks and libraries updated
- โข Use security linters and analyzers
๐ Standards & Specifications
W3C Trusted Types Specification
The official W3C specification defining the Trusted Types API for preventing DOM XSS attacks.
View W3C Specification โContent Security Policy Level 3
CSP3 specification including require-trusted-types-for directive.
View CSP3 Specification โOWASP XSS Prevention Cheat Sheet
Comprehensive guide for preventing XSS vulnerabilities in web applications.
View OWASP Guide โMDN Web Docs - Trusted Types
Developer-friendly documentation with examples and browser compatibility.
View MDN Documentation โ๐ Advanced Topics
Migration Strategies
Migrating existing applications to Trusted Types:
- โข Start with report-only mode to identify violations
- โข Create default policies for gradual migration
- โข Refactor dangerous sink usage incrementally
- โข Use automated tools to find violations
- โข Test thoroughly in staging environments
Performance Considerations
Optimizing Trusted Types implementation:
- โข Cache policy creation to avoid overhead
- โข Minimize policy complexity for better performance
- โข Use efficient sanitization libraries
- โข Profile and benchmark policy execution
- โข Consider lazy loading for non-critical policies
Browser Support & Polyfills
Current implementation status across major browsers:
Browser | Version | Support |
---|---|---|
Chrome/Edge | 83+ | โ Full |
Firefox | In Development | ๐ง Tracking |
Safari | 15.4+ | โ Full |
Polyfill | Available | โก Limited |
๐ Note: Firefox implementation is actively being developed. Track progress on Bug 1508286 . Consider using feature detection and progressive enhancement for cross-browser compatibility.
Integration with Frameworks
Using Trusted Types with modern frameworks:
- โข React: Use DOMPurify with dangerouslySetInnerHTML
- โข Angular: Built-in Trusted Types support in v11+
- โข Vue: Custom directives for safe HTML binding
- โข Svelte: Compile-time XSS prevention
- โข Web Components: Shadow DOM security boundaries
๐ Privacy & Analytics
Our Commitment to Privacy
This demo uses privacy-friendly analytics to understand usage patterns and improve the educational experience. We respect your privacy and follow these principles:
- โข No personal data collection - We don't collect names, emails, or any PII
- โข IP anonymization - IP addresses are anonymized before processing
- โข No cross-site tracking - Analytics are limited to this demo only
- โข No advertising - Data is never used for advertising purposes
- โข Cookie-free option - Analytics work without storing cookies
What We Track
We collect anonymous usage data to improve the demo:
- โข Page views and navigation patterns
- โข Demo feature interactions (which examples are tested)
- โข Code copying events (to understand useful examples)
- โข Browser and device types (for compatibility)
- โข General geographic region (country level only)
Your Control
You have full control over analytics on this site. You can change your preference at any time: