xssProtection.js•6.17 kB
// xssProtection.js - XSS sanitization functions for secure HTML processing
// Future: DOMPurify/Sanitizer API integration planned
// ===== PERFORMANCE OPTIMIZATION =====
const REGEX_CACHE = {
    htmlEntities: /&(?:lt|gt|quot|#x0*3[ce]);?/gi,
    dangerousTags: /<(script|style)[^>]*>.*?<\/\1>/gis,
    svgElements: /<\/?(?:svg|animate|animateMotion|animateTransform|set|foreignObject|math|annotation-xml)[^>]*(?:\/>|>.*?<\/(?:svg|animate|animateMotion|animateTransform|set|foreignObject|math|annotation-xml)>)?/gis,
    metaRefresh: /<meta[^>]*(?:http-equiv\s*=\s*["']?refresh["']?|content\s*=\s*["'][^"']*(?:javascript|data):[^"']*["'])[^>]*\/?>/gis,
    formElements: /<\/?(?:iframe|object|embed|form|input|textarea|select)(?:\s[^>]*)?(?:\/>|>.*?<\/(?:iframe|object|embed|form|textarea|select)>)?/gis,
    dangerousButtons: /<button(?!\s[^>]*class\s*=\s*["'][^"']*code-window-copy-btn[^"']*["'])[^>]*>.*?<\/button>/gis,
    dangerousAttrs: /\s*(?:on\w+|formaction|srcdoc|background|lowsrc)\s*=\s*(?:["'][^"']*["']|[^"'\s>]*)|(?:javascript|vbscript|data):[^"'\s>]*|expression\s*\(|data\s*:\s*[^,;]*[;,]/gi
};
let tempElement = null;
function getTempElement() {
    if (!tempElement) {
        tempElement = document.createElement('div');
    }
    tempElement.innerHTML = '';
    return tempElement;
}
// ===== SANITIZATION FUNCTIONS =====
function sanitizeText(input) {
    if (typeof input !== 'string') return '';
    
    const div = getTempElement();
    div.textContent = input;
    const result = div.innerHTML;
    div.textContent = '';
    return result;
}
function sanitizeHTML(input) {
    if (typeof input !== 'string') return '';
    
    let sanitized = input;
    sanitized = sanitized.replace(/</gi, '<').replace(/>/gi, '>').replace(/"/gi, '"').replace(/�*3c;?/gi, '<').replace(/�*3e;?/gi, '>');
    sanitized = sanitized.replace(REGEX_CACHE.dangerousTags, '');
    sanitized = sanitized.replace(REGEX_CACHE.metaRefresh, '');
    sanitized = sanitized.replace(REGEX_CACHE.svgElements, '');
    sanitized = sanitized.replace(REGEX_CACHE.formElements, '');   
    sanitized = sanitized.replace(REGEX_CACHE.dangerousButtons, '');
    sanitized = sanitized.replace(REGEX_CACHE.dangerousAttrs, '');
    return sanitized;
}
function sanitizeHTMLStrict(input) {
    if (typeof input !== 'string') return '';
    
    const allowedTags = ['p', 'br', 'strong', 'b', 'em', 'i', 'u', 'a', 'ul', 'ol', 'li', 'code', 'pre', 'span'];
    const allowedAttributes = {
        'a': ['href', 'title'],
        'code': ['class'],
        'pre': ['class'],
        'span': ['class']
    };
    
    let sanitized = sanitizeHTML(input);
    const temp = document.createElement('div');
    temp.innerHTML = sanitized;
    
    function cleanElement(element) {
        const tagName = element.tagName.toLowerCase();
        
        if (element.namespaceURI === "http://www.w3.org/2000/svg") {
            element.remove();
            return;
        }
        
        if (!allowedTags.includes(tagName)) {
            if (element.childNodes.length > 0) {
                element.replaceWith(...element.childNodes);
            } else {
                element.remove();
            }
            return;
        }
        
        const allowedAttrs = allowedAttributes[tagName] || [];
        Array.from(element.attributes).forEach(attr => {
            if (!allowedAttrs.includes(attr.name)) {
                element.removeAttribute(attr.name);
            }
        });
        
        if (tagName === 'a' && element.hasAttribute('href')) {
            const href = element.getAttribute('href');
            if (!href.match(/^https?:\/\//)) {
                element.removeAttribute('href');
            }
        }
        
        if (['code', 'pre', 'span'].includes(tagName) && element.hasAttribute('class')) {
            const className = element.getAttribute('class');
            if (!className.match(/^[a-zA-Z0-9\-_\s]+$/)) {
                element.removeAttribute('class');
            }
        }
        
        Array.from(element.children).forEach(cleanElement);
    }
    
    Array.from(temp.children).forEach(cleanElement);
    
    const svgCheck = temp.querySelectorAll('*');
    svgCheck.forEach(el => {
        if (el.namespaceURI === "http://www.w3.org/2000/svg") {
            el.remove();
        }
        if (el.tagName && ['foreignObject', 'math', 'annotation-xml'].includes(el.tagName.toLowerCase())) {
            el.remove();
        }
    });
    
    return temp.innerHTML;
}
// ===== DOM INSERTION FUNCTIONS =====
function safeInsertHTML(element, content, useSanitization = true) {
    if (!element || !content) return;
    
    if (useSanitization) {
        element.innerHTML = sanitizeHTML(content);
    } else {
        element.textContent = content;
    }
}
function insertChatMessage(container, message, isHTML = false) {
    if (!container || !message) return;
    
    const messageElement = document.createElement('div');
    messageElement.className = 'chat-message';
    
    if (isHTML) {
        messageElement.innerHTML = sanitizeHTMLStrict(message);
    } else {
        messageElement.textContent = message;
    }
    
    container.appendChild(messageElement);
    return messageElement;
}
// ===== MODULE EXPORTS =====
export {
    sanitizeText,
    sanitizeHTML,
    sanitizeHTMLStrict,
    safeInsertHTML,
    insertChatMessage
};
if (typeof window !== 'undefined') {
    const isDevelopment = typeof process !== 'undefined' && process.env?.NODE_ENV === 'development' ||
                         window.location?.hostname === 'localhost' ||
                         window.location?.hostname === '127.0.0.1' ||
                         window.location?.protocol === 'file:';
    
    if (isDevelopment) {
        window.__DEV_MARMSecurity = {
            sanitizeText,
            sanitizeHTML,
            sanitizeHTMLStrict,
            safeInsertHTML,
            insertChatMessage,
            _validateInput: (input) => {
                // Validation function for development
            }
        };
        
        window.MARMSecurity = window.__DEV_MARMSecurity;
    }
}