✨ Features: - 🧠 Shadow learner that builds intelligence from command patterns - 🛡️ Smart command validation with safety checks - 💾 Automatic context monitoring and backup system - 🔄 Session continuity across Claude restarts 📚 Documentation: - Complete Diátaxis-organized documentation - Learning-oriented tutorial for getting started - Task-oriented how-to guides for specific problems - Information-oriented reference for quick lookup - Understanding-oriented explanations of architecture 🚀 Installation: - One-command installation script - Bootstrap prompt for installation via Claude - Cross-platform compatibility - Comprehensive testing suite 🎯 Ready for real-world use and community feedback! 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
6.4 KiB
Python
Executable File
184 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Command Validator Hook - PreToolUse[Bash] hook
|
|
Validates bash commands using shadow learner insights
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import re
|
|
import os
|
|
from pathlib import Path
|
|
|
|
# Add lib directory to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
|
|
|
|
from shadow_learner import ShadowLearner
|
|
from models import ValidationResult
|
|
|
|
|
|
class CommandValidator:
|
|
"""Validates bash commands for safety and success probability"""
|
|
|
|
def __init__(self):
|
|
self.shadow_learner = ShadowLearner()
|
|
|
|
# Dangerous command patterns
|
|
self.dangerous_patterns = [
|
|
r'rm\s+-rf\s+/', # Delete root
|
|
r'mkfs\.', # Format filesystem
|
|
r'dd\s+if=.*of=/dev/', # Overwrite devices
|
|
r':(){ :|:& };:', # Fork bomb
|
|
r'curl.*\|\s*bash', # Pipe to shell
|
|
r'wget.*\|\s*sh', # Pipe to shell
|
|
r';.*rm\s+-rf', # Command chaining with rm
|
|
r'&&.*rm\s+-rf', # Command chaining with rm
|
|
]
|
|
|
|
self.suspicious_patterns = [
|
|
r'sudo\s+rm', # Sudo with rm
|
|
r'chmod\s+777', # Overly permissive
|
|
r'/etc/passwd', # System files
|
|
r'/etc/shadow', # System files
|
|
r'nc.*-l.*-p', # Netcat listener
|
|
]
|
|
|
|
def validate_command_safety(self, command: str) -> ValidationResult:
|
|
"""Comprehensive command safety validation"""
|
|
|
|
# Normalize command for analysis
|
|
normalized = command.lower().strip()
|
|
|
|
# Check for dangerous patterns
|
|
for pattern in self.dangerous_patterns:
|
|
if re.search(pattern, normalized):
|
|
return ValidationResult(
|
|
allowed=False,
|
|
reason=f"Dangerous command pattern detected",
|
|
severity="critical"
|
|
)
|
|
|
|
# Check for suspicious patterns
|
|
for pattern in self.suspicious_patterns:
|
|
if re.search(pattern, normalized):
|
|
return ValidationResult(
|
|
allowed=True, # Allow but warn
|
|
reason=f"Suspicious command pattern detected",
|
|
severity="warning"
|
|
)
|
|
|
|
return ValidationResult(allowed=True, reason="Command appears safe")
|
|
|
|
def validate_with_shadow_learner(self, command: str) -> ValidationResult:
|
|
"""Use shadow learner to predict command success"""
|
|
|
|
try:
|
|
prediction = self.shadow_learner.predict_command_outcome(command)
|
|
|
|
if not prediction["likely_success"] and prediction["confidence"] > 0.8:
|
|
suggestions = prediction.get("suggestions", [])
|
|
suggestion_text = f" Try: {suggestions[0]}" if suggestions else ""
|
|
|
|
return ValidationResult(
|
|
allowed=False,
|
|
reason=f"Command likely to fail (confidence: {prediction['confidence']:.0%}){suggestion_text}",
|
|
severity="medium",
|
|
suggestions=suggestions
|
|
)
|
|
elif prediction["warnings"]:
|
|
return ValidationResult(
|
|
allowed=True,
|
|
reason=prediction["warnings"][0],
|
|
severity="warning",
|
|
suggestions=prediction.get("suggestions", [])
|
|
)
|
|
|
|
except Exception:
|
|
# If shadow learner fails, don't block
|
|
pass
|
|
|
|
return ValidationResult(allowed=True, reason="No issues detected")
|
|
|
|
def validate_command(self, command: str) -> ValidationResult:
|
|
"""Main validation entry point"""
|
|
|
|
# Safety validation (blocking)
|
|
safety_result = self.validate_command_safety(command)
|
|
if not safety_result.allowed:
|
|
return safety_result
|
|
|
|
# Shadow learner validation (predictive)
|
|
prediction_result = self.validate_with_shadow_learner(command)
|
|
|
|
# Return most significant result
|
|
if prediction_result.severity in ["high", "critical"]:
|
|
return prediction_result
|
|
elif safety_result.severity in ["warning", "medium"]:
|
|
return safety_result
|
|
else:
|
|
return prediction_result
|
|
|
|
|
|
def main():
|
|
"""Main hook entry point"""
|
|
try:
|
|
# Read input from Claude Code
|
|
input_data = json.loads(sys.stdin.read())
|
|
|
|
# Extract command from parameters
|
|
tool = input_data.get("tool", "")
|
|
parameters = input_data.get("parameters", {})
|
|
command = parameters.get("command", "")
|
|
|
|
if tool != "Bash" or not command:
|
|
# Not a bash command, allow it
|
|
response = {"allow": True, "message": "Not a bash command"}
|
|
print(json.dumps(response))
|
|
sys.exit(0)
|
|
|
|
# Validate command
|
|
validator = CommandValidator()
|
|
result = validator.validate_command(command)
|
|
|
|
if not result.allowed:
|
|
# Block the command
|
|
response = {
|
|
"allow": False,
|
|
"message": f"⛔ Command blocked: {result.reason}"
|
|
}
|
|
print(json.dumps(response))
|
|
sys.exit(1) # Exit code 1 = block operation
|
|
|
|
elif result.severity in ["warning", "medium"]:
|
|
# Allow with warning
|
|
warning_emoji = "⚠️" if result.severity == "warning" else "🚨"
|
|
message = f"{warning_emoji} {result.reason}"
|
|
|
|
if result.suggestions:
|
|
message += f"\n💡 Suggestion: {result.suggestions[0]}"
|
|
|
|
response = {
|
|
"allow": True,
|
|
"message": message
|
|
}
|
|
print(json.dumps(response))
|
|
sys.exit(0)
|
|
|
|
else:
|
|
# Allow without warning
|
|
response = {"allow": True, "message": "Command validated"}
|
|
print(json.dumps(response))
|
|
sys.exit(0)
|
|
|
|
except Exception as e:
|
|
# Never block on validation errors - always allow operation
|
|
response = {
|
|
"allow": True,
|
|
"message": f"Validation error: {str(e)}"
|
|
}
|
|
print(json.dumps(response))
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |