#!/usr/bin/env node /** * Command Validator Hook - PreToolUse[Bash] hook * Validates bash commands using shadow learner insights */ const fs = require('fs-extra'); const path = require('path'); // Add lib directory to require path const libPath = path.join(__dirname, '..', 'lib'); const { ShadowLearner } = require(path.join(libPath, 'shadow-learner')); class CommandValidator { constructor() { this.shadowLearner = new ShadowLearner(); // Dangerous command patterns this.dangerousPatterns = [ /rm\s+-rf\s+\//, // Delete root /mkfs\./, // Format filesystem /dd\s+if=.*of=\/dev\//, // Overwrite devices /:\(\){ :\|:& };:/, // Fork bomb /curl.*\|\s*bash/, // Pipe to shell /wget.*\|\s*sh/, // Pipe to shell /;.*rm\s+-rf/, // Command chaining with rm /&&.*rm\s+-rf/, // Command chaining with rm ]; this.suspiciousPatterns = [ /sudo\s+rm/, // Sudo with rm /chmod\s+777/, // Overly permissive /\/etc\/passwd/, // System files /\/etc\/shadow/, // System files /nc.*-l.*-p/, // Netcat listener ]; } /** * Comprehensive command safety validation */ validateCommandSafety(command) { // Normalize command for analysis const normalized = command.toLowerCase().trim(); // Check for dangerous patterns for (const pattern of this.dangerousPatterns) { if (pattern.test(normalized)) { return { allowed: false, reason: 'Dangerous command pattern detected', severity: 'critical' }; } } // Check for suspicious patterns for (const pattern of this.suspiciousPatterns) { if (pattern.test(normalized)) { return { allowed: true, // Allow but warn reason: 'Suspicious command pattern detected', severity: 'warning' }; } } return { allowed: true, reason: 'Command appears safe' }; } /** * Use shadow learner to predict command success */ validateWithShadowLearner(command) { try { const prediction = this.shadowLearner.predictCommandOutcome(command); if (!prediction.likelySuccess && prediction.confidence > 0.8) { const suggestions = prediction.suggestions || []; const suggestionText = suggestions.length > 0 ? ` Try: ${suggestions[0]}` : ''; return { allowed: false, reason: `Command likely to fail (confidence: ${Math.round(prediction.confidence * 100)}%)${suggestionText}`, severity: 'medium', suggestions }; } else if (prediction.warnings && prediction.warnings.length > 0) { return { allowed: true, reason: prediction.warnings[0], severity: 'warning', suggestions: prediction.suggestions || [] }; } } catch (error) { // If shadow learner fails, don't block } return { allowed: true, reason: 'No issues detected' }; } /** * Main validation entry point */ validateCommand(command) { // Safety validation (blocking) const safetyResult = this.validateCommandSafety(command); if (!safetyResult.allowed) { return safetyResult; } // Shadow learner validation (predictive) const predictionResult = this.validateWithShadowLearner(command); // Return most significant result if (['high', 'critical'].includes(predictionResult.severity)) { return predictionResult; } else if (['warning', 'medium'].includes(safetyResult.severity)) { return safetyResult; } else { return predictionResult; } } } async function main() { try { let inputData = ''; // Handle stdin input if (process.stdin.isTTY) { // If called directly for testing inputData = JSON.stringify({ tool: 'Bash', parameters: { command: 'pip install requests' } }); } else { // Read from stdin process.stdin.setEncoding('utf8'); for await (const chunk of process.stdin) { inputData += chunk; } } const input = JSON.parse(inputData); // Extract command from parameters const tool = input.tool || ''; const parameters = input.parameters || {}; const command = parameters.command || ''; if (tool !== 'Bash' || !command) { // Not a bash command, allow it const response = { allow: true, message: 'Not a bash command' }; console.log(JSON.stringify(response)); process.exit(0); } // Validate command const validator = new CommandValidator(); const result = validator.validateCommand(command); if (!result.allowed) { // Block the command const response = { allow: false, message: `⛔ Command blocked: ${result.reason}` }; console.log(JSON.stringify(response)); process.exit(1); // Exit code 1 = block operation } else if (['warning', 'medium'].includes(result.severity)) { // Allow with warning const warningEmoji = result.severity === 'warning' ? '⚠️' : '🚨'; let message = `${warningEmoji} ${result.reason}`; if (result.suggestions && result.suggestions.length > 0) { message += `\n💡 Suggestion: ${result.suggestions[0]}`; } const response = { allow: true, message }; console.log(JSON.stringify(response)); process.exit(0); } else { // Allow without warning const response = { allow: true, message: 'Command validated' }; console.log(JSON.stringify(response)); process.exit(0); } } catch (error) { // Never block on validation errors - always allow operation const response = { allow: true, message: `Validation error: ${error.message}` }; console.log(JSON.stringify(response)); process.exit(0); } } // Handle unhandled promise rejections process.on('unhandledRejection', (error) => { const response = { allow: true, message: `Validation error: ${error.message}` }; console.log(JSON.stringify(response)); process.exit(0); }); main();