๐Ÿ›ก๏ธ Trusted Types Demo

Complete Guide to XSS Prevention with Implementation Code

Interactive demonstrations with working code examples

๐Ÿงช Interactive Security Demonstrations with Code

1

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

๐Ÿ’ป 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(/)<[^<]*)*<\/script>/gi, '')
      // Remove event handlers
      .replace(/\son\w+\s*=\s*["'][^"']*["']/gi, '')
      .replace(/\son\w+\s*=\s*[^\s>]*/gi, '')
      // Remove javascript: protocol
      .replace(/javascript:/gi, '')
      // Remove data: protocol (except images)
      .replace(/data:(?!image\/)/gi, '');
    
    console.log('Sanitized:', cleaned);
    return cleaned;
  }
});
// Safe HTML injection with Trusted Types
function injectHTMLSafely(userInput) {
  const container = document.getElementById('output');
  
  // Method 1: Using the policy directly
  const trustedHTML = htmlSanitizationPolicy.createHTML(userInput);
  container.innerHTML = trustedHTML;
  
  // Method 2: Using textContent for plain text
  // container.textContent = userInput; // No HTML rendering
  
  // Method 3: Using DOMPurify with Trusted Types
  // if (window.DOMPurify) {
  //   const clean = DOMPurify.sanitize(userInput, {
  //     RETURN_TRUSTED_TYPE: true
  //   });
  //   container.innerHTML = clean;
  // }
}

// Event handler for the demo
document.getElementById('injectBtn').addEventListener('click', () => {
  const userInput = document.getElementById('htmlInput').value;
  injectHTMLSafely(userInput);
});
// โŒ UNSAFE - Direct innerHTML assignment
function unsafeInject(userInput) {
  // This will be blocked by Trusted Types!
  element.innerHTML = userInput; // Throws TypeError
  element.outerHTML = userInput; // Also blocked
  element.insertAdjacentHTML('beforeend', userInput); // Blocked
}

// โœ… SAFE - Using Trusted Types
function safeInject(userInput) {
  // Create trusted HTML through policy
  const trustedHTML = policy.createHTML(userInput);
  element.innerHTML = trustedHTML; // Safe!
}

// โœ… SAFE - Using textContent for plain text
function setTextContent(element, userInput) {
  element.textContent = userInput; // No HTML rendering
}

// โœ… SAFE - Using DOM methods
function createSafeElement(tagName, textContent) {
  const element = document.createElement(tagName);
  element.textContent = textContent;
  return element;
}

// โœ… SAFE - Escaping HTML entities
function escapeHTML(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

// Use textContent for plain text
setTextContent(element, userInput);

Sanitized Output:

2

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:

3

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:

4

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:

โœ… Trusted Types initialized. Security policies are active.

๐Ÿ“ฆ Polyfilling Trusted Types

๐ŸŽฏ Quick Start

Trusted Types is natively supported in Chromium 83+ and Safari 15.4+. For other browsers, use the official polyfill:

<!-- Add before your application code -->
<script src="https://cdn.jsdelivr.net/npm/trusted-types@latest/dist/es5/trustedtypes.api_only.build.js"
        integrity="sha384-XqVn80KjGxKBXv4lAWYmf2L3DGfqFRGPwr8HQxvLzXpQPL5j5UTc5hyLT8vfVZGg"
        crossorigin="anonymous"></script>

๐Ÿ“ฅ Installation Methods

# Install the polyfill
npm install trusted-types

# For TypeScript projects, also install type definitions
npm install --save-dev @types/trusted-types

# Or using Yarn
yarn add trusted-types
yarn add -D @types/trusted-types
<!-- API-only version (recommended for most apps) -->
<script src="https://cdn.jsdelivr.net/npm/trusted-types@latest/dist/es5/trustedtypes.api_only.build.js"></script>

<!-- Or from unpkg -->
<script src="https://unpkg.com/trusted-types@latest/dist/es5/trustedtypes.api_only.build.js"></script>

<!-- Full version with enforcement (requires CSP configuration) -->
<script src="https://cdn.jsdelivr.net/npm/trusted-types@latest/dist/es5/trustedtypes.build.js"
        data-csp="require-trusted-types-for 'script'"></script>
// Ultra-lightweight fallback (1 line!)
// Add this before any code that uses Trusted Types
if(typeof trustedTypes == 'undefined') {
  window.trustedTypes = {
    createPolicy: (name, rules) => rules
  };
}

// This provides basic compatibility but NO enforcement
// Policies will return plain strings instead of TrustedType objects

โš›๏ธ React Integration with TypeScript

// types/trusted-types.d.ts
declare global {
  interface Window {
    trustedTypes?: TrustedTypePolicyFactory;
  }
}

// utils/trustedTypes.ts
import DOMPurify from 'dompurify';

export interface SecurityPolicy {
  createHTML(input: string): TrustedHTML | string;
  createScript(input: string): TrustedScript | string;
  createScriptURL(input: string): TrustedScriptURL | string;
}

export const createTrustedTypesPolicy = (name: string = 'react-app'): TrustedTypePolicy | null => {
  if (typeof window !== 'undefined' && window.trustedTypes) {
    return window.trustedTypes.createPolicy(name, {
      createHTML: (input: string) => DOMPurify.sanitize(input),
      createScript: (input: string) => input,
      createScriptURL: (url: string) => {
        const allowedDomains = [
          'https://cdn.jsdelivr.net',
          'https://unpkg.com',
          window.location.origin
        ];
        
        const urlObj = new URL(url, window.location.origin);
        if (allowedDomains.some(domain => urlObj.href.startsWith(domain))) {
          return url;
        }
        throw new Error(`Untrusted URL: ${url}`);
      }
    });
  }
  return null;
};

// polyfills/index.ts
import 'trusted-types';

// Initialize default policy for React
if (typeof window !== 'undefined') {
  const policy = createTrustedTypesPolicy('default');
  
  // Feature detection
  if (!window.trustedTypes) {
    console.warn('Trusted Types not natively supported, using polyfill');
  }
}
// components/SafeHTML.tsx
import React, { useEffect, useRef, memo } from 'react';
import DOMPurify from 'dompurify';

interface SafeHTMLProps {
  content: string;
  className?: string;
  tagName?: keyof JSX.IntrinsicElements;
}

const SafeHTML = memo(({ 
  content, 
  className, 
  tagName = 'div' 
}) => {
  const containerRef = useRef(null);
  const policyRef = useRef(null);

  useEffect(() => {
    // Create policy once on mount
    if (window.trustedTypes && !policyRef.current) {
      try {
        policyRef.current = window.trustedTypes.createPolicy('safe-html', {
          createHTML: (input: string) => DOMPurify.sanitize(input, {
            ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'li', 'ol'],
            ALLOWED_ATTR: ['href', 'target', 'rel']
          })
        });
      } catch (e) {
        console.warn('Failed to create Trusted Types policy:', e);
      }
    }
  }, []);

  useEffect(() => {
    if (!containerRef.current) return;

    try {
      if (policyRef.current) {
        // Use Trusted Types policy
        const trustedHTML = policyRef.current.createHTML(content);
        containerRef.current.innerHTML = trustedHTML as string;
      } else {
        // Fallback for browsers without Trusted Types
        containerRef.current.innerHTML = DOMPurify.sanitize(content);
      }
    } catch (error) {
      console.error('Failed to set HTML content:', error);
      containerRef.current.textContent = content; // Safe fallback
    }
  }, [content]);

  const Tag = tagName as any;
  return ;
});

SafeHTML.displayName = 'SafeHTML';

export default SafeHTML;

// Usage Example:
// 
// hooks/useTrustedTypes.ts
import { useMemo, useCallback, useRef, useEffect, useState } from 'react';
import DOMPurify from 'dompurify';

interface UseTrustedTypesOptions {
  policyName?: string;
  allowedTags?: string[];
  allowedAttributes?: string[];
}

export const useTrustedHTML = (
  dirty: string,
  options: UseTrustedTypesOptions = {}
): { __html: string } => {
  const { 
    policyName = 'react-html',
    allowedTags,
    allowedAttributes 
  } = options;

  return useMemo(() => {
    const sanitizeOptions = {
      ALLOWED_TAGS: allowedTags,
      ALLOWED_ATTR: allowedAttributes
    };

    if (window.trustedTypes) {
      try {
        const policy = window.trustedTypes.createPolicy(policyName, {
          createHTML: (input: string) => DOMPurify.sanitize(input, sanitizeOptions)
        });
        return { __html: policy.createHTML(dirty) as string };
      } catch (e) {
        console.warn('Trusted Types policy creation failed:', e);
      }
    }
    
    // Fallback to DOMPurify without Trusted Types
    return { __html: DOMPurify.sanitize(dirty, sanitizeOptions) };
  }, [dirty, policyName, allowedTags, allowedAttributes]);
};

// Hook for managing script URLs
export const useTrustedScriptURL = (url: string): string | null => {
  const [trustedURL, setTrustedURL] = useState(null);
  
  useEffect(() => {
    if (window.trustedTypes) {
      try {
        const policy = window.trustedTypes.createPolicy('script-loader', {
          createScriptURL: (input: string) => {
            const allowedOrigins = [
              'https://cdn.jsdelivr.net',
              'https://unpkg.com',
              window.location.origin
            ];
            
            const urlObj = new URL(input, window.location.origin);
            if (allowedOrigins.some(origin => urlObj.href.startsWith(origin))) {
              return input;
            }
            throw new Error(`Untrusted script URL: ${input}`);
          }
        });
        
        const trusted = policy.createScriptURL(url);
        setTrustedURL(trusted as string);
      } catch (e) {
        console.error('Failed to create trusted script URL:', e);
        setTrustedURL(null);
      }
    } else {
      // No Trusted Types support, return original URL
      setTrustedURL(url);
    }
  }, [url]);
  
  return trustedURL;
};

// Usage in component:
const MyComponent: React.FC<{ htmlContent: string }> = ({ htmlContent }) => {
  const trustedContent = useTrustedHTML(htmlContent, {
    allowedTags: ['p', 'a', 'strong', 'em'],
    allowedAttributes: ['href', 'target']
  });
  
  return 
; };
// pages/_document.tsx (Next.js)
import { Html, Head, Main, NextScript } from 'next/document';

export default function Document() {
  return (
    
      
        {/* Content Security Policy with Trusted Types */}
        
        
        {/* Load polyfill before any other scripts */}