@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
580 lines • 20.6 kB
JavaScript
/**
* @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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
/**
* 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