UNPKG

@bernotieno/mini-framework

Version:

A lightweight JavaScript framework built from scratch with zero dependencies

126 lines (105 loc) 3.43 kB
/** * DOM Security Module * Handles XSS protection, attribute sanitization, and security validation */ export class DOMSecurity { constructor() { this.allowedTags = new Set([ 'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'img', 'ul', 'ol', 'li', 'table', 'tr', 'td', 'th', 'form', 'input', 'button', 'textarea', 'select', 'option', 'header', 'footer', 'main', 'section', 'article', 'nav', 'aside', 'figure', 'figcaption', 'time', 'mark', 'small', 'strong', 'em', 'code', 'pre', 'blockquote', 'cite', 'label', 'fieldset', 'legend', 'details', 'summary' ]); } /** * Escape HTML entities to prevent XSS */ escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Validate if a tag is safe to use */ isTagAllowed(tag) { return this.allowedTags.has(tag.toLowerCase().trim()); } /** * Sanitize attributes to prevent XSS and other security issues */ sanitizeAttributes(attrs) { if (!attrs || typeof attrs !== 'object') { return {}; } const sanitized = {}; for (const [key, value] of Object.entries(attrs)) { if (typeof key !== 'string' || key.trim() === '') { console.warn('Invalid attribute name:', key); continue; } const normalizedKey = key.toLowerCase().trim(); // Block dangerous attributes that could contain scripts if (normalizedKey.startsWith('on') && typeof value === 'string') { console.warn(`Blocked potentially dangerous attribute: ${normalizedKey}`); continue; } // Block javascript: URLs if ((normalizedKey === 'href' || normalizedKey === 'src') && typeof value === 'string' && value.toLowerCase().trim().startsWith('javascript:')) { console.warn(`Blocked javascript: URL in ${normalizedKey}`); continue; } // Block unsafe data: URLs if (normalizedKey === 'src' && typeof value === 'string') { const lowerValue = value.toLowerCase().trim(); if (lowerValue.startsWith('data:') && !lowerValue.startsWith('data:image/')) { console.warn('Blocked potentially unsafe data: URL'); continue; } } // Validate style attribute if (normalizedKey === 'style' && typeof value === 'string') { sanitized[normalizedKey] = this.sanitizeStyle(value); continue; } sanitized[normalizedKey] = value; } return sanitized; } /** * Sanitize CSS style string */ sanitizeStyle(style) { if (typeof style !== 'string') return ''; const dangerous = [ 'expression', 'javascript:', 'vbscript:', 'data:', 'behavior:', '-moz-binding', 'import' ]; let sanitized = style; dangerous.forEach(danger => { const regex = new RegExp(danger, 'gi'); sanitized = sanitized.replace(regex, ''); }); return sanitized; } /** * Validate attribute values for specific security checks */ validateAttribute(key, value) { if (key === 'id' && !/^[a-zA-Z][\w-]*$/.test(String(value))) { console.warn(`Invalid ID format: ${value}`); return false; } if (key === 'class' && String(value).includes('<')) { console.warn('Potentially unsafe class value blocked'); return false; } return true; } }