UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

580 lines 20.6 kB
/** * @fileoverview OrdoJS Security Manager - Comprehensive security implementation */ import {} from '../types/index.js'; /** * Default security configuration */ const DEFAULT_SECURITY_CONFIG = { enableXSSProtection: true, enableCSRFProtection: true, enableCSP: true, enableInputValidation: true, enableHTMLEscaping: true, enableSQLInjectionProtection: true, enablePathTraversalProtection: true, cspDirectives: { 'default-src': ["'self'"], 'script-src': ["'self'", "'unsafe-inline'"], 'style-src': ["'self'", "'unsafe-inline'"], 'img-src': ["'self'", "data:", "https:"], 'font-src': ["'self'", "https:"], 'object-src': ["'none'"], 'media-src': ["'self'"], 'frame-src': ["'none'"], 'worker-src': ["'self'"], 'connect-src': ["'self'"], 'frame-ancestors': ["'self'"], 'base-uri': ["'self'"], 'form-action': ["'self'"], 'upgrade-insecure-requests': true }, allowedHTMLTags: [ 'div', 'span', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'img', 'button', 'input', 'textarea', 'select', 'option', 'form', 'label', 'table', 'tr', 'td', 'th' ], allowedHTMLAttributes: [ 'class', 'id', 'style', 'src', 'href', 'alt', 'title', 'type', 'name', 'value', 'placeholder', 'disabled', 'readonly', 'required', 'maxlength', 'minlength', 'pattern' ], maxInputLength: 10000, maxNestedDepth: 10 }; /** * Comprehensive security manager for OrdoJS applications */ export class SecurityManager { config; csrfTokens = new Map(); xssPatterns = []; sqlInjectionPatterns = []; pathTraversalPatterns = []; constructor(config = {}) { this.config = { ...DEFAULT_SECURITY_CONFIG, ...config }; this.initializeSecurityPatterns(); } /** * Analyze component for security vulnerabilities */ analyzeComponent(ast) { const warnings = []; const errors = []; const recommendations = []; try { // Analyze markup for XSS vulnerabilities if (this.config.enableXSSProtection) { const xssResult = this.analyzeXSSVulnerabilities(ast.component.markupBlock); if (xssResult.xssDetected) { errors.push(`XSS vulnerability detected: ${xssResult.detectedPatterns.join(', ')}`); recommendations.push('Sanitize user input before rendering'); recommendations.push('Use proper HTML escaping for dynamic content'); } } // Analyze server functions for injection vulnerabilities if (ast.component.serverBlock) { const injectionResult = this.analyzeInjectionVulnerabilities(ast.component.serverBlock); if (injectionResult.sqlInjectionDetected) { errors.push('Potential SQL injection vulnerability detected'); recommendations.push('Use parameterized queries'); recommendations.push('Validate and sanitize all user inputs'); } if (injectionResult.pathTraversalDetected) { errors.push('Potential path traversal vulnerability detected'); recommendations.push('Validate file paths'); recommendations.push('Use path normalization'); } } // Analyze client functions for security issues if (ast.component.clientBlock) { const clientResult = this.analyzeClientSecurity(ast.component.clientBlock); warnings.push(...clientResult.warnings); recommendations.push(...clientResult.recommendations); } // Check for missing security headers const headerResult = this.checkSecurityHeaders(); if (headerResult.missingHeaders.length > 0) { warnings.push(`Missing security headers: ${headerResult.missingHeaders.join(', ')}`); recommendations.push('Implement proper security headers'); } return { passed: errors.length === 0, warnings, errors, recommendations }; } catch (error) { return { passed: false, warnings: [], errors: [`Security analysis failed: ${error instanceof Error ? error.message : String(error)}`], recommendations: ['Review security configuration'] }; } } /** * Generate CSRF token */ generateCSRFToken(sessionId) { if (!this.config.enableCSRFProtection) { return { token: '', expiresAt: new Date(), isValid: false }; } const token = this.generateSecureToken(); const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours this.csrfTokens.set(sessionId, { token, expiresAt }); return { token, expiresAt, isValid: true }; } /** * Validate CSRF token */ validateCSRFToken(sessionId, token) { if (!this.config.enableCSRFProtection) { return true; } const stored = this.csrfTokens.get(sessionId); if (!stored) { return false; } if (new Date() > stored.expiresAt) { this.csrfTokens.delete(sessionId); return false; } return stored.token === token; } /** * Validate and sanitize input */ validateInput(input, type) { if (!this.config.enableInputValidation) { return { isValid: true, errors: [], sanitizedInput: input, warnings: [] }; } const errors = []; const warnings = []; let sanitizedInput = input; try { // Check input length if (typeof input === 'string' && input.length > this.config.maxInputLength) { errors.push(`Input length (${input.length}) exceeds maximum allowed (${this.config.maxInputLength})`); sanitizedInput = input.substring(0, this.config.maxInputLength); } // Validate based on type switch (type) { case 'string': sanitizedInput = this.sanitizeString(input); break; case 'number': sanitizedInput = this.sanitizeNumber(input); break; case 'boolean': sanitizedInput = this.sanitizeBoolean(input); break; case 'object': sanitizedInput = this.sanitizeObject(input); break; case 'array': sanitizedInput = this.sanitizeArray(input); break; } // Check for XSS patterns if (typeof sanitizedInput === 'string') { const xssResult = this.detectXSS(sanitizedInput); if (xssResult.xssDetected) { errors.push('XSS pattern detected in input'); sanitizedInput = xssResult.escapedContent; } } // Check for SQL injection patterns if (typeof sanitizedInput === 'string') { const sqlResult = this.detectSQLInjection(sanitizedInput); if (sqlResult.detected) { errors.push('SQL injection pattern detected in input'); } } // Check for path traversal patterns if (typeof sanitizedInput === 'string') { const pathResult = this.detectPathTraversal(sanitizedInput); if (pathResult.detected) { errors.push('Path traversal pattern detected in input'); } } } catch (error) { errors.push(`Input validation failed: ${error instanceof Error ? error.message : String(error)}`); } return { isValid: errors.length === 0, errors, sanitizedInput, warnings }; } /** * Generate Content Security Policy header */ generateCSPHeader() { if (!this.config.enableCSP) { return ''; } const directives = []; for (const [directive, values] of Object.entries(this.config.cspDirectives)) { if (Array.isArray(values)) { directives.push(`${directive} ${values.join(' ')}`); } else if (typeof values === 'boolean') { if (values) { directives.push(directive); } } } return directives.join('; '); } /** * Escape HTML content */ escapeHTML(content) { if (!this.config.enableHTMLEscaping) { return content; } return content .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#x27;') .replace(/\//g, '&#x2F;'); } /** * Sanitize HTML content */ sanitizeHTML(content) { // Remove script tags and event handlers let sanitized = content .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '') .replace(/javascript:/gi, '') .replace(/vbscript:/gi, ''); // Only allow specified tags and attributes const allowedTags = this.config.allowedHTMLTags.join('|'); const allowedAttributes = this.config.allowedHTMLAttributes.join('|'); // This is a simplified sanitization - in production, use a proper HTML sanitizer const tagRegex = new RegExp(`<(?!/?(${allowedTags})\b)[^>]*>`, 'gi'); sanitized = sanitized.replace(tagRegex, ''); return sanitized; } /** * Initialize security patterns */ initializeSecurityPatterns() { // XSS patterns this.xssPatterns = [ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, /javascript:/gi, /vbscript:/gi, /on\w+\s*=/gi, /<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, /<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, /<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi ]; // SQL injection patterns this.sqlInjectionPatterns = [ /(\b(union|select|insert|update|delete|drop|create|alter|exec|execute)\b)/gi, /(--|\/\*|\*\/|;)/g, /(\b(and|or)\b\s+\d+\s*=\s*\d+)/gi, /(\b(and|or)\b\s+['"][^'"]*['"]\s*=\s*['"][^'"]*['"])/gi ]; // Path traversal patterns this.pathTraversalPatterns = [ /\.\.\//g, /\.\.\\/g, /%2e%2e%2f/gi, /%2e%2e%5c/gi, /\.\.%2f/gi, /\.\.%5c/gi ]; } /** * Analyze XSS vulnerabilities in markup */ analyzeXSSVulnerabilities(markupBlock) { const detectedPatterns = []; let xssDetected = false; const analyzeElement = (element) => { // Check attributes for XSS patterns for (const attr of element.attributes) { if (typeof attr.value === 'string') { const xssResult = this.detectXSS(attr.value); if (xssResult.xssDetected) { xssDetected = true; detectedPatterns.push(`XSS in attribute ${attr.name}: ${attr.value}`); } } } // Check children for (const child of element.children) { if (child.type === 'HTMLElement') { analyzeElement(child); } else if (child.type === 'Interpolation') { const interpolation = child; // Check if interpolation contains user input if (this.containsUserInput(interpolation.expression)) { detectedPatterns.push('User input in interpolation without proper escaping'); xssDetected = true; } } } }; // Analyze all elements for (const element of markupBlock.elements) { analyzeElement(element); } return { xssDetected, detectedPatterns, sanitizedContent: '', escapedContent: '' }; } /** * Analyze injection vulnerabilities in server functions */ analyzeInjectionVulnerabilities(serverBlock) { let sqlInjectionDetected = false; let pathTraversalDetected = false; for (const func of serverBlock.functions) { // Analyze function body for potential vulnerabilities for (const stmt of func.body) { if (stmt.expression) { const exprStr = JSON.stringify(stmt.expression); // Check for SQL injection patterns for (const pattern of this.sqlInjectionPatterns) { if (pattern.test(exprStr)) { sqlInjectionDetected = true; } } // Check for path traversal patterns for (const pattern of this.pathTraversalPatterns) { if (pattern.test(exprStr)) { pathTraversalDetected = true; } } } } } return { sqlInjectionDetected, pathTraversalDetected }; } /** * Analyze client-side security */ analyzeClientSecurity(clientBlock) { const warnings = []; const recommendations = []; // Check for eval usage for (const func of clientBlock.functions) { const funcStr = JSON.stringify(func); if (funcStr.includes('eval(') || funcStr.includes('Function(')) { warnings.push('eval() or Function() usage detected'); recommendations.push('Avoid using eval() or Function() for security'); } } // Check for innerHTML usage for (const func of clientBlock.functions) { const funcStr = JSON.stringify(func); if (funcStr.includes('innerHTML')) { warnings.push('innerHTML usage detected'); recommendations.push('Use textContent instead of innerHTML when possible'); } } return { warnings, recommendations }; } /** * Check for missing security headers */ checkSecurityHeaders() { const missingHeaders = []; if (this.config.enableCSP) { missingHeaders.push('Content-Security-Policy'); } if (this.config.enableXSSProtection) { missingHeaders.push('X-XSS-Protection'); } return { missingHeaders }; } /** * Detect XSS patterns in content */ detectXSS(content) { const detectedPatterns = []; let xssDetected = false; for (const pattern of this.xssPatterns) { if (pattern.test(content)) { xssDetected = true; detectedPatterns.push(pattern.source); } } return { xssDetected, detectedPatterns, sanitizedContent: this.sanitizeHTML(content), escapedContent: this.escapeHTML(content) }; } /** * Detect SQL injection patterns */ detectSQLInjection(content) { const patterns = []; let detected = false; for (const pattern of this.sqlInjectionPatterns) { if (pattern.test(content)) { detected = true; patterns.push(pattern.source); } } return { detected, patterns }; } /** * Detect path traversal patterns */ detectPathTraversal(content) { const patterns = []; let detected = false; for (const pattern of this.pathTraversalPatterns) { if (pattern.test(content)) { detected = true; patterns.push(pattern.source); } } return { detected, patterns }; } /** * Check if expression contains user input */ containsUserInput(expression) { // This is a simplified check - in a real implementation, you'd do more sophisticated analysis const exprStr = JSON.stringify(expression); return exprStr.includes('user') || exprStr.includes('input') || exprStr.includes('data'); } /** * Generate secure token */ generateSecureToken() { const array = new Uint8Array(32); crypto.getRandomValues(array); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); } /** * Sanitize string input */ sanitizeString(input) { if (typeof input !== 'string') { throw new Error('Expected string input'); } return this.escapeHTML(input.trim()); } /** * Sanitize number input */ sanitizeNumber(input) { const num = Number(input); if (isNaN(num)) { throw new Error('Invalid number input'); } return num; } /** * Sanitize boolean input */ sanitizeBoolean(input) { if (typeof input === 'boolean') { return input; } if (typeof input === 'string') { const lower = input.toLowerCase(); if (lower === 'true' || lower === '1') return true; if (lower === 'false' || lower === '0') return false; } throw new Error('Invalid boolean input'); } /** * Sanitize object input */ sanitizeObject(input) { if (typeof input !== 'object' || input === null || Array.isArray(input)) { throw new Error('Expected object input'); } const sanitized = {}; let depth = 0; const sanitizeRecursive = (obj, currentDepth) => { if (currentDepth > this.config.maxNestedDepth) { throw new Error('Object nesting depth exceeded'); } for (const [key, value] of Object.entries(obj)) { if (typeof value === 'string') { sanitized[key] = this.sanitizeString(value); } else if (typeof value === 'number') { sanitized[key] = this.sanitizeNumber(value); } else if (typeof value === 'boolean') { sanitized[key] = this.sanitizeBoolean(value); } else if (typeof value === 'object' && value !== null) { sanitized[key] = sanitizeRecursive(value, currentDepth + 1); } else { sanitized[key] = value; } } return sanitized; }; return sanitizeRecursive(input, depth); } /** * Sanitize array input */ sanitizeArray(input) { if (!Array.isArray(input)) { throw new Error('Expected array input'); } return input.map((item, index) => { if (typeof item === 'string') { return this.sanitizeString(item); } else if (typeof item === 'number') { return this.sanitizeNumber(item); } else if (typeof item === 'boolean') { return this.sanitizeBoolean(item); } else if (typeof item === 'object' && item !== null) { return this.sanitizeObject(item); } else if (Array.isArray(item)) { return this.sanitizeArray(item); } return item; }); } } //# sourceMappingURL=security-manager.js.map