upskill-event-manager/lib/security/SecureCommandExecutor.js
Ben c3e7fe9140 feat: comprehensive HVAC plugin development framework and modernization
## Major Enhancements

### 🏗️ Architecture & Infrastructure
- Implement comprehensive Docker testing infrastructure with hermetic environment
- Add Forgejo Actions CI/CD pipeline for automated deployments
- Create Page Object Model (POM) testing architecture reducing test duplication by 90%
- Establish security-first development patterns with input validation and output escaping

### 🧪 Testing Framework Modernization
- Migrate 146+ tests from 80 duplicate files to centralized architecture
- Add comprehensive E2E test suites for all user roles and workflows
- Implement WordPress error detection with automatic site health monitoring
- Create robust browser lifecycle management with proper cleanup

### 📚 Documentation & Guides
- Add comprehensive development best practices guide
- Create detailed administrator setup documentation
- Establish user guides for trainers and master trainers
- Document security incident reports and migration guides

### 🔧 Core Plugin Features
- Enhance trainer profile management with certification system
- Improve find trainer functionality with advanced filtering
- Strengthen master trainer area with content management
- Add comprehensive venue and organizer management

### 🛡️ Security & Reliability
- Implement security-first patterns throughout codebase
- Add comprehensive input validation and output escaping
- Create secure credential management system
- Establish proper WordPress role-based access control

### 🎯 WordPress Integration
- Strengthen singleton pattern implementation across all classes
- Enhance template hierarchy with proper WordPress integration
- Improve page manager with hierarchical URL structure
- Add comprehensive shortcode and menu system

### 🔍 Developer Experience
- Add extensive debugging and troubleshooting tools
- Create comprehensive test data seeding scripts
- Implement proper error handling and logging
- Establish consistent code patterns and standards

### 📊 Performance & Optimization
- Optimize database queries and caching strategies
- Improve asset loading and script management
- Enhance template rendering performance
- Streamline user experience across all interfaces

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 11:26:10 -03:00

432 lines
No EOL
14 KiB
JavaScript

/**
* HVAC Testing Framework - Secure Command Execution
*
* Provides secure command execution with input validation, parameterization,
* and command allowlisting to prevent injection vulnerabilities.
*
* Security Features:
* - Command allowlisting with parameter validation
* - Input sanitization and validation
* - Subprocess execution with proper isolation
* - Audit logging of all command executions
* - Timeout and resource limits
*
* @author Claude Code - Emergency Security Response
* @version 1.0.0
* @security CRITICAL - Prevents command injection vulnerabilities
*/
const { spawn, execFile } = require('child_process');
const path = require('path');
const { getCredentialManager } = require('./SecureCredentialManager');
class SecureCommandExecutor {
constructor() {
this.credentialManager = getCredentialManager();
this.allowedCommands = this.initializeCommandAllowlist();
this.executionLog = [];
}
/**
* Initialize allowlist of safe commands with their parameter patterns
* @returns {Map} Command allowlist configuration
*/
initializeCommandAllowlist() {
return new Map([
// WordPress CLI commands
['wp', {
binary: 'wp',
allowedSubcommands: [
'rewrite flush',
'eval',
'user list',
'user get',
'option get',
'option update',
'db query',
'plugin list',
'theme list'
],
parameterValidation: {
'eval': /^['"][^'";&|`$()]*['"]$/,
'user get': /^[a-zA-Z0-9@._-]+$/,
'option get': /^[a-zA-Z0-9_-]+$/,
'option update': /^[a-zA-Z0-9_-]+\s+['"][^'";&|`$()]*['"]$/,
'db query': /^['"]SELECT\s+[^'";&|`$()]*['"]$/
},
timeout: 30000,
maxOutputSize: 1024 * 1024 // 1MB
}],
// Node.js and npm commands
['node', {
binary: 'node',
allowedSubcommands: [
'--version',
'-e'
],
parameterValidation: {
'-e': /^['"]console\.log\([^'";&|`$()]*\)['"]$/
},
timeout: 10000,
maxOutputSize: 64 * 1024 // 64KB
}],
// Playwright commands
['npx', {
binary: 'npx',
allowedSubcommands: [
'playwright install',
'playwright test'
],
parameterValidation: {
'playwright test': /^[a-zA-Z0-9/_.-]+\.js$/
},
timeout: 120000,
maxOutputSize: 10 * 1024 * 1024 // 10MB
}],
// System information commands (read-only)
['ls', {
binary: 'ls',
allowedSubcommands: ['-la', '-l', ''],
parameterValidation: {
'': /^[a-zA-Z0-9/_.-]+$/,
'-l': /^[a-zA-Z0-9/_.-]+$/,
'-la': /^[a-zA-Z0-9/_.-]+$/
},
timeout: 5000,
maxOutputSize: 64 * 1024
}],
['cat', {
binary: 'cat',
allowedSubcommands: [''],
parameterValidation: {
'': /^[a-zA-Z0-9/_.-]+\.(log|txt|json)$/
},
timeout: 5000,
maxOutputSize: 1024 * 1024
}]
]);
}
/**
* Execute a command securely with validation and isolation
* @param {string} command - Command to execute
* @param {Array} args - Command arguments
* @param {Object} options - Execution options
* @returns {Promise<Object>} Execution result
*/
async executeCommand(command, args = [], options = {}) {
// Validate command is in allowlist
const commandConfig = this.allowedCommands.get(command);
if (!commandConfig) {
throw new Error(`Command not allowed: ${command}`);
}
// Validate subcommand and parameters
await this.validateCommandParameters(command, args, commandConfig);
// Prepare execution environment
const executionOptions = this.prepareExecutionOptions(commandConfig, options);
// Log command execution attempt
const executionId = this.logCommandExecution(command, args, options);
try {
// Execute command with isolation
const result = await this.executeWithIsolation(
commandConfig.binary,
args,
executionOptions
);
// Log successful execution
this.logCommandResult(executionId, 'SUCCESS', result);
return result;
} catch (error) {
// Log failed execution
this.logCommandResult(executionId, 'FAILED', { error: error.message });
throw error;
}
}
/**
* Validate command parameters against allowlist patterns
* @param {string} command - Base command
* @param {Array} args - Command arguments
* @param {Object} commandConfig - Command configuration
*/
async validateCommandParameters(command, args, commandConfig) {
if (args.length === 0) return;
const subcommand = args[0];
const fullSubcommand = args.join(' ');
// Check if subcommand is allowed
const isAllowed = commandConfig.allowedSubcommands.some(allowed => {
if (allowed === '') return true;
if (allowed === subcommand) return true;
if (fullSubcommand.startsWith(allowed)) return true;
return false;
});
if (!isAllowed) {
throw new Error(`Subcommand not allowed: ${command} ${fullSubcommand}`);
}
// Validate parameters against patterns
if (commandConfig.parameterValidation) {
for (const [pattern, regex] of Object.entries(commandConfig.parameterValidation)) {
if (fullSubcommand.startsWith(pattern)) {
const parameterPart = fullSubcommand.substring(pattern.length).trim();
if (parameterPart && !regex.test(parameterPart)) {
throw new Error(`Invalid parameters for ${command} ${pattern}: ${parameterPart}`);
}
}
}
}
// Additional security checks
await this.performSecurityChecks(args);
}
/**
* Perform additional security checks on command arguments
* @param {Array} args - Command arguments
*/
async performSecurityChecks(args) {
const dangerousPatterns = [
/[;&|`$(){}]/, // Shell metacharacters
/\.\./, // Directory traversal
/\/etc\/passwd/, // System file access
/rm\s+-rf/, // Destructive commands
/sudo/, // Privilege escalation
/chmod/, // Permission changes
/curl.*http/, // External network access
/wget.*http/ // External downloads
];
const fullCommand = args.join(' ');
for (const pattern of dangerousPatterns) {
if (pattern.test(fullCommand)) {
throw new Error(`Security violation: dangerous pattern detected in command`);
}
}
}
/**
* Prepare secure execution options
* @param {Object} commandConfig - Command configuration
* @param {Object} userOptions - User-provided options
* @returns {Object} Execution options
*/
prepareExecutionOptions(commandConfig, userOptions) {
return {
timeout: commandConfig.timeout,
maxBuffer: commandConfig.maxOutputSize,
env: this.createSecureEnvironment(),
shell: false, // Disable shell to prevent injection
stdio: ['pipe', 'pipe', 'pipe'],
...userOptions
};
}
/**
* Create secure execution environment
* @returns {Object} Environment variables
*/
createSecureEnvironment() {
// Start with minimal environment
const secureEnv = {
PATH: '/usr/local/bin:/usr/bin:/bin',
NODE_ENV: process.env.NODE_ENV || 'development',
HOME: process.env.HOME
};
// Add WordPress CLI path if needed
if (process.env.WP_CLI_PATH) {
secureEnv.WP_CLI_PATH = process.env.WP_CLI_PATH;
}
return secureEnv;
}
/**
* Execute command with proper isolation
* @param {string} binary - Command binary
* @param {Array} args - Arguments
* @param {Object} options - Execution options
* @returns {Promise<Object>} Execution result
*/
async executeWithIsolation(binary, args, options) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
let stdout = '';
let stderr = '';
const child = spawn(binary, args, options);
// Set up timeout
const timeoutId = setTimeout(() => {
child.kill('SIGTERM');
reject(new Error(`Command timeout after ${options.timeout}ms`));
}, options.timeout);
// Collect output with size limits
child.stdout.on('data', (data) => {
stdout += data.toString();
if (stdout.length > options.maxBuffer) {
child.kill('SIGTERM');
reject(new Error('Output size limit exceeded'));
}
});
child.stderr.on('data', (data) => {
stderr += data.toString();
if (stderr.length > options.maxBuffer) {
child.kill('SIGTERM');
reject(new Error('Error output size limit exceeded'));
}
});
child.on('close', (code) => {
clearTimeout(timeoutId);
const duration = Date.now() - startTime;
if (code === 0) {
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: code,
duration
});
} else {
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
}
});
child.on('error', (error) => {
clearTimeout(timeoutId);
reject(error);
});
});
}
/**
* Log command execution for audit trail
* @param {string} command - Command name
* @param {Array} args - Arguments
* @param {Object} options - Options
* @returns {string} Execution ID
*/
logCommandExecution(command, args, options) {
const executionId = require('crypto').randomUUID();
const logEntry = {
executionId,
timestamp: new Date().toISOString(),
command,
args: args.map(arg => this.sanitizeForLogging(arg)),
options: this.sanitizeOptionsForLogging(options),
status: 'STARTED'
};
this.executionLog.push(logEntry);
this.credentialManager.auditLogger('COMMAND_EXECUTION_STARTED', logEntry);
return executionId;
}
/**
* Log command execution result
* @param {string} executionId - Execution ID
* @param {string} status - Execution status
* @param {Object} result - Execution result
*/
logCommandResult(executionId, status, result) {
const logEntry = {
executionId,
timestamp: new Date().toISOString(),
status,
duration: result.duration,
exitCode: result.exitCode,
outputSize: result.stdout ? result.stdout.length : 0,
errorSize: result.stderr ? result.stderr.length : 0
};
this.executionLog.push(logEntry);
this.credentialManager.auditLogger('COMMAND_EXECUTION_COMPLETED', logEntry);
}
/**
* Sanitize arguments for logging (remove sensitive data)
* @param {string} arg - Argument to sanitize
* @returns {string} Sanitized argument
*/
sanitizeForLogging(arg) {
// Remove potential passwords and sensitive data
return arg.replace(/password[=:]\s*[^\s]+/gi, 'password=***')
.replace(/token[=:]\s*[^\s]+/gi, 'token=***')
.replace(/key[=:]\s*[^\s]+/gi, 'key=***');
}
/**
* Sanitize execution options for logging
* @param {Object} options - Options to sanitize
* @returns {Object} Sanitized options
*/
sanitizeOptionsForLogging(options) {
const sanitized = { ...options };
if (sanitized.env) {
sanitized.env = Object.keys(sanitized.env);
}
return sanitized;
}
/**
* WordPress-specific secure command execution
* @param {string} wpCommand - WP-CLI command
* @param {Array} args - Additional arguments
* @returns {Promise<Object>} Command result
*/
async executeWordPressCommand(wpCommand, args = []) {
const wpArgs = wpCommand.split(' ').concat(args);
return await this.executeCommand('wp', wpArgs);
}
/**
* Get execution audit log
* @returns {Array} Complete execution log
*/
getExecutionLog() {
return [...this.executionLog];
}
/**
* Clear execution log (for cleanup)
*/
clearExecutionLog() {
this.executionLog = [];
}
}
// Singleton instance
let commandExecutorInstance = null;
/**
* Get singleton instance of SecureCommandExecutor
* @returns {SecureCommandExecutor}
*/
function getCommandExecutor() {
if (!commandExecutorInstance) {
commandExecutorInstance = new SecureCommandExecutor();
}
return commandExecutorInstance;
}
module.exports = {
SecureCommandExecutor,
getCommandExecutor
};