codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
585 lines (521 loc) • 17.8 kB
text/typescript
/**
* Security Utilities - Consolidated security functions
* Replaces: security-utils.ts and scattered security code
*/
import { createHash, randomBytes } from 'crypto';
import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import { resolve, normalize } from 'path';
import { logger } from './logger.js';
import { SecurityValidation, SecurityError } from './types.js';
export interface SecurityConfig {
enableSandbox: boolean;
maxInputLength: number;
allowedCommands: string[];
allowedPaths: string[];
blockedPatterns: RegExp[];
scanDependencies: boolean;
enableCSPProtection: boolean;
}
export class SecurityUtils {
private config: SecurityConfig;
private blockedPatterns: RegExp[];
private allowedCommands: Set<string>;
constructor(config: Partial<SecurityConfig> = {}) {
this.config = {
enableSandbox: true,
maxInputLength: 50000, // Increased for code file analysis
allowedCommands: ['npm', 'node', 'git', 'tsc', 'eslint'],
allowedPaths: [process.cwd()],
blockedPatterns: [
/rm\s+-rf\s+\/[^/\s]*/gi, // Dangerous delete commands with actual paths
/sudo\s+rm/gi, // Sudo with dangerous commands
/eval\s*\(\s*[^)]*\$_/gi, // Dynamic code evaluation with user input
/exec\s*\(\s*[^)]*\$_/gi, // Dynamic code execution with user input
/system\s*\(\s*[^)]*\$_/gi, // Dynamic system calls with user input
/shell_exec\s*\(\s*[^)]*\$_/gi, // Dynamic shell execution with user input
/curl\s+.*\|\s*sh/gi, // Remote execution
/wget\s+.*\|\s*sh/gi, // Remote execution
/powershell\s+-[Cc]ommand/gi, // PowerShell remote execution
/cmd\s+\/c\s+.*&/gi, // Chained command execution
/net\s+user\s+.*\/add/gi, // User creation
/reg\s+add\s+HKEY/gi, // Registry modification
/schtasks/gi, // Task scheduling
/wmic/gi, // WMI commands
/format\s+c:/gi, // Format commands
/del\s+\/[sqa]/gi, // Dangerous delete flags
/taskkill\s+\/f/gi, // Force kill processes
/shutdown/gi, // System shutdown
/reboot/gi, // System reboot
/halt/gi, // System halt
/chmod\s+777/gi, // Dangerous permissions
/chown\s+root/gi, // Ownership changes
/passwd/gi, // Password changes
/su\s+/gi, // Switch user
/mount\s+/gi, // Mount operations
/umount\s+/gi, // Unmount operations
/dd\s+if=/gi, // Disk operations
/fdisk/gi, // Disk partitioning
/mkfs/gi, // File system creation
/cryptsetup/gi, // Encryption operations
],
scanDependencies: true,
enableCSPProtection: true,
...config,
};
this.blockedPatterns = this.config.blockedPatterns;
this.allowedCommands = new Set(this.config.allowedCommands);
}
/**
* Validate user input for security threats
*/
async validateInput(input: string): Promise<SecurityValidation> {
try {
// Check input length
if (input.length > this.config.maxInputLength) {
return {
isValid: false,
reason: `Input exceeds maximum length of ${this.config.maxInputLength} characters`,
riskLevel: 'medium',
};
}
// Check if this is legitimate code analysis context
const isCodeAnalysis = this.isLegitimateCodeAnalysis(input);
// Check for blocked patterns (skip if legitimate code analysis)
if (!isCodeAnalysis) {
for (const pattern of this.blockedPatterns) {
if (pattern.test(input)) {
logger.warn('🚨 Blocked potentially dangerous input pattern', {
pattern: pattern.source,
});
return {
isValid: false,
reason: `Input contains potentially dangerous pattern: ${pattern.source}`,
riskLevel: 'high',
};
}
}
}
// Check for suspicious file operations (skip if legitimate code analysis)
if (!isCodeAnalysis) {
const suspiciousFileOps = [
/\.\.\//, // Directory traversal
/\/etc\/passwd/, // System files
/\/proc\//, // Process information
/\/sys\//, // System information
/C:\\Windows\\/, // Windows system
/\/bin\//, // System binaries
];
for (const pattern of suspiciousFileOps) {
if (pattern.test(input)) {
return {
isValid: false,
reason: 'Input contains suspicious file operation patterns',
riskLevel: 'high',
};
}
}
}
// Sanitize and validate
const sanitizedInput = this.sanitizeInput(input);
return {
isValid: true,
sanitizedInput,
riskLevel: 'low',
};
} catch (error) {
logger.error('Error validating input:', error);
return {
isValid: false,
reason: 'Input validation failed',
riskLevel: 'high',
};
}
}
/**
* Sanitize user input
*/
private sanitizeInput(input: string): string {
// Remove null bytes
let sanitized = input.replace(/\0/g, '');
// Normalize line endings
sanitized = sanitized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
// Limit consecutive newlines
sanitized = sanitized.replace(/\n{5,}/g, '\n\n\n\n');
// Remove or escape potentially dangerous sequences
sanitized = sanitized.replace(/`{3,}/g, '```'); // Limit code blocks
return sanitized.trim();
}
/**
* Validate command execution
*/
async validateCommand(command: string, args: string[] = []): Promise<SecurityValidation> {
const baseCommand = command.split(' ')[0];
if (!this.allowedCommands.has(baseCommand)) {
return {
isValid: false,
reason: `Command '${baseCommand}' is not in the allowed commands list`,
riskLevel: 'high',
};
}
// Check for command injection patterns
const fullCommand = `${command} ${args.join(' ')}`;
const injectionPatterns = [
/[;&|`$(){}]/, // Command separators and substitution
/>\s*\/dev\//, // Redirecting to device files
/\|\s*sh/, // Piping to shell
/\|\s*bash/, // Piping to bash
];
for (const pattern of injectionPatterns) {
if (pattern.test(fullCommand)) {
return {
isValid: false,
reason: 'Command contains potential injection patterns',
riskLevel: 'high',
};
}
}
return {
isValid: true,
riskLevel: 'low',
};
}
/**
* Validate file path for access
*/
async validatePath(filePath: string): Promise<SecurityValidation> {
try {
const normalizedPath = normalize(resolve(filePath));
// Check if path is within allowed directories
const isAllowed = this.config.allowedPaths.some(allowedPath => {
const normalizedAllowed = normalize(resolve(allowedPath));
return normalizedPath.startsWith(normalizedAllowed);
});
if (!isAllowed) {
return {
isValid: false,
reason: 'Path is outside allowed directories',
riskLevel: 'high',
};
}
// Check for directory traversal and dangerous patterns
const dangerousPatterns = [
/\.\.[/\\]/, // Directory traversal
/~[/\\]/, // Home directory access
/[/\\][.]+[/\\]/, // Hidden directory access
/\$\{/, // Variable substitution
/%[0-9A-Fa-f]{2}/, // URL encoded characters
/\\x[0-9A-Fa-f]{2}/, // Hex encoded characters
/\\u[0-9A-Fa-f]{4}/, // Unicode encoded characters
];
for (const pattern of dangerousPatterns) {
if (pattern.test(filePath)) {
return {
isValid: false,
reason: 'Path contains dangerous traversal or encoding patterns',
riskLevel: 'high',
};
}
}
// Check for system file access
const systemPaths = [
'/etc',
'/proc',
'/sys',
'/dev',
'/root',
'/boot',
'/var/log',
'/var/run',
'/tmp',
'/var/tmp',
'C:\\Windows',
'C:\\System32',
'C:\\Program Files',
'C:\\ProgramData',
'C:\\Users\\All Users',
'C:\\$Recycle.Bin',
'C:\\Recovery',
];
for (const systemPath of systemPaths) {
if (normalizedPath.startsWith(systemPath)) {
return {
isValid: false,
reason: 'Attempted access to system directories',
riskLevel: 'high',
};
}
}
return {
isValid: true,
riskLevel: 'low',
};
} catch (error) {
return {
isValid: false,
reason: 'Path validation failed',
riskLevel: 'medium',
};
}
}
/**
* Execute command in sandbox
*/
async executeSecurely(
command: string,
args: string[] = [],
options: { timeout?: number; cwd?: string } = {}
): Promise<{ success: boolean; output: string; error?: string }> {
if (!this.config.enableSandbox) {
logger.error('🚨 CRITICAL: Attempted to execute command with sandbox disabled', {
command,
args,
});
throw new SecurityError('Sandbox execution is REQUIRED and cannot be disabled');
}
// Validate command
const commandValidation = await this.validateCommand(command, args);
if (!commandValidation.isValid) {
throw new SecurityError(`Command validation failed: ${commandValidation.reason}`);
}
// Validate working directory
if (options.cwd) {
const pathValidation = await this.validatePath(options.cwd);
if (!pathValidation.isValid) {
throw new SecurityError(`Working directory validation failed: ${pathValidation.reason}`);
}
}
return new Promise(resolve => {
const timeout = options.timeout || 30000;
let output = '';
let error = '';
const child = spawn(command, args, {
cwd: options.cwd || process.cwd(),
stdio: ['ignore', 'pipe', 'pipe'],
timeout,
// Add additional security constraints
env: {
PATH: process.env.PATH,
NODE_ENV: process.env.NODE_ENV || 'production',
},
});
child.stdout?.on('data', data => {
output += data.toString();
});
child.stderr?.on('data', data => {
error += data.toString();
});
child.on('close', code => {
resolve({
success: code === 0,
output: output.trim(),
error: error.trim() || undefined,
});
});
child.on('error', err => {
resolve({
success: false,
output: '',
error: err.message,
});
});
// Kill process if it exceeds timeout
setTimeout(() => {
if (!child.killed) {
child.kill('SIGTERM');
setTimeout(() => {
if (!child.killed) {
child.kill('SIGKILL');
}
}, 5000);
}
}, timeout);
});
}
/**
* Scan dependencies for known vulnerabilities
*/
async scanDependencies(packageJsonPath: string): Promise<{
vulnerabilities: Array<{
package: string;
version: string;
severity: 'low' | 'medium' | 'high' | 'critical';
description: string;
recommendation: string;
}>;
riskLevel: 'low' | 'medium' | 'high';
}> {
if (!this.config.scanDependencies) {
return { vulnerabilities: [], riskLevel: 'low' };
}
try {
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
const vulnerabilities = [];
// Known problematic packages
const knownIssues = {
vm2: {
severity: 'high' as const,
description: 'vm2 is deprecated and has known security vulnerabilities',
recommendation: 'Use isolated-vm or proper sandboxing alternatives',
},
lodash: {
severity: 'medium' as const,
description: 'Some versions of lodash have prototype pollution vulnerabilities',
recommendation: 'Update to latest version or use lodash-es',
},
};
for (const [pkg, version] of Object.entries(dependencies)) {
if (pkg in knownIssues) {
vulnerabilities.push({
package: pkg,
version: version as string,
...knownIssues[pkg as keyof typeof knownIssues],
});
}
}
// Determine overall risk level
const riskLevel = vulnerabilities.some(v => v.severity === 'high')
? 'high'
: vulnerabilities.some(v => v.severity === 'medium')
? 'medium'
: 'low';
return { vulnerabilities, riskLevel };
} catch (error) {
logger.error('Error scanning dependencies:', error);
return { vulnerabilities: [], riskLevel: 'medium' };
}
}
/**
* Generate secure random token
*/
generateSecureToken(length: number = 32): string {
return randomBytes(length).toString('hex');
}
/**
* Hash sensitive data
*/
hashSensitiveData(data: string, salt?: string): string {
const actualSalt = salt || randomBytes(16).toString('hex');
return createHash('sha256')
.update(data + actualSalt)
.digest('hex');
}
/**
* Validate API key format
*/
validateApiKey(apiKey: string): boolean {
// Basic validation - should be alphanumeric and reasonable length
const apiKeyPattern = /^[A-Za-z0-9_-]{16,128}$/;
return apiKeyPattern.test(apiKey);
}
/**
* Apply Content Security Policy headers for web interfaces
*/
getCSPHeaders(): Record<string, string> {
if (!this.config.enableCSPProtection) {
return {};
}
return {
'Content-Security-Policy': [
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self'",
"font-src 'self'",
"frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
].join('; '),
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
};
}
/**
* Update security configuration
*/
updateConfig(newConfig: Partial<SecurityConfig>): void {
// SECURITY: Never allow disabling sandbox in production
if (newConfig.enableSandbox === false && process.env.NODE_ENV === 'production') {
logger.error('🚨 SECURITY VIOLATION: Attempted to disable sandbox in production');
throw new SecurityError('Cannot disable sandbox in production environment');
}
// SECURITY: Validate that critical security settings are not weakened
if (newConfig.maxInputLength && newConfig.maxInputLength > 50000) {
logger.warn('⚠️ Warning: Increasing max input length beyond recommended limit');
}
// SECURITY: Log all security configuration changes
const changes = Object.keys(newConfig);
logger.warn('🔒 Security configuration updated', {
changes,
timestamp: new Date().toISOString(),
});
this.config = { ...this.config, ...newConfig };
this.allowedCommands = new Set(this.config.allowedCommands);
this.blockedPatterns = this.config.blockedPatterns;
}
/**
* Get current security configuration (sanitized)
*/
getConfig(): Omit<SecurityConfig, 'blockedPatterns'> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { blockedPatterns, ...config } = this.config;
return config;
}
private isLegitimateCodeAnalysis(input: string): boolean {
// Check for code analysis indicators
const analysisPatterns = [
/analyze\s+this\s+.*code/gi,
/analyze\s+this\s+\w+\s+code\s+file/gi,
/code\s+analysis/gi,
/file:\s+.*\.(js|ts|py|java|cpp|c|h|css|html|json)/gi,
/content:\s*```/gi,
/project\s+structure/gi,
/codebase/gi,
/overview\s+of\s+the\s+project/gi,
/technology\s+stack/gi,
/brief\s+summary/gi,
/key\s+functions/gi,
/quality\s+assessment/gi,
/potential\s+issues/gi,
/security\s+concerns/gi,
/\.(js|ts|tsx|jsx|py|java|cpp|c|h|css|html|json|md|yml|yaml)\s+code\s+file/gi,
// Voice archetype patterns
/You are .* Voice.*focused on.*analysis/gi,
/analyzer voice/gi,
/specialized.*CLI agent/gi,
/performance analysis/gi,
/code quality assessment/gi,
/architectural analysis/gi,
/security analysis/gi,
/file analysis/gi,
/src\/.*\.ts/gi, // Source file paths
/dist\/.*\.js/gi, // Distribution file paths
];
// Check for legitimate code patterns
const codePatterns = [
/^function\s+\w+\s*\([^)]*\)\s*:\s*\w+\s*{/gi, // TypeScript functions
/^const\s+\w+\s*=\s*\([^)]*\)\s*=>/gi, // Arrow functions
/^class\s+\w+\s*{/gi, // Class definitions
/^interface\s+\w+\s*{/gi, // Interface definitions
/^type\s+\w+\s*=/gi, // Type definitions
/^import\s+.*from\s+/gi, // Import statements
/^export\s+(default\s+)?/gi, // Export statements
/\/\^.*\$\/[gimuy]*\.test\(/gi, // Regex test methods
/return\s+\/\^.*\$\/[gimuy]*\.test\(/gi, // Return regex test
];
// Check if input looks like legitimate code
const looksLikeCode = codePatterns.some(pattern => pattern.test(input.trim()));
if (looksLikeCode) {
return true;
}
return analysisPatterns.some(pattern => pattern.test(input));
}
}