-
Notifications
You must be signed in to change notification settings - Fork 0
command pattern
The Command Pattern is extensively implemented throughout the HawkEye CLI interface to encapsulate requests as objects, enabling parameterization of clients with different requests, queuing of requests, and support for undo operations. This pattern provides a clean separation between the code that invokes an operation and the code that performs the operation.
The Command Pattern:
- Encapsulates a request as an object with all information needed to perform the action
- Decouples the object that invokes the operation from the object that performs it
- Allows parameterization of objects with different requests
- Supports queuing, logging, and undo operations
- Enables macro recording and batch processing
HawkEye implements the Command Pattern primarily through its CLI architecture built on the Click framework. The implementation includes:
- CLI Command Hierarchy - Hierarchical command structure with groups and subcommands
- Context Management - Shared state management across command invocations
- Parameter Processing - Command-specific parameter handling and validation
- Error Handling - Consistent error handling across all commands
- Command Composition - Support for command chaining and pipeline operations
classDiagram
class HawkEyeCLI {
<<click.Group>>
+cli(ctx, verbose, quiet, log_file, config_file)
+info(ctx)
+quick_scan(ctx, target, ports, threads, timeout, output, format)
+show_banner()
+main()
}
class HawkEyeGroup {
<<click.Group>>
+format_help(ctx, formatter)
+invoke(ctx)
}
class HawkEyeContext {
+settings: Settings
+logger: Logger
+verbose: bool
+quiet: bool
+output_file: str
+setup_logging(verbose, quiet, log_file)
}
class ScanCommand {
<<click.Command>>
+scan(ctx, target, ports, tcp, udp, threads, timeout, output, format)
+validate_target(target) None
+parse_ports(ports_str) List[int]
}
class DetectCommand {
<<click.Group>>
+detect()
+mcp(ctx, target, enable_introspection, timeout, output, format)
+local(ctx, process_names, config_paths, output, format)
}
class AnalyzeThreatCommand {
<<click.Command>>
+analyze_threats(ctx, input_file, output, model, provider, temperature)
+_validate_input_file(file_path) bool
+_select_ai_provider(provider_name) AIProvider
}
class ReportCommand {
<<click.Group>>
+report()
+generate(ctx, input, output, format, title, author, organization)
+summary(ctx, input, output, include_charts, include_recommendations)
}
class ConfigCommand {
<<click.Group>>
+config()
+show(ctx, section)
+set(ctx, key, value)
+validate(ctx, config_file)
}
HawkEyeCLI --> HawkEyeGroup : uses
HawkEyeCLI --> HawkEyeContext : manages
HawkEyeCLI --> ScanCommand : contains
HawkEyeCLI --> DetectCommand : contains
HawkEyeCLI --> AnalyzeThreatCommand : contains
HawkEyeCLI --> ReportCommand : contains
HawkEyeCLI --> ConfigCommand : contains
classDiagram
class Command {
<<interface>>
+execute(context: CommandContext)* CommandResult
+validate_params(params: Dict)* bool
+get_help_text()* str
}
class CommandContext {
+settings: Settings
+logger: Logger
+parameters: Dict[str, Any]
+output_formatter: OutputFormatter
+error_handler: ErrorHandler
+get_parameter(key: str) Any
+set_parameter(key: str, value: Any)
+format_output(data: Any, format: str) str
}
class ScanCommandImpl {
-scanner: BaseScanner
-target_enumerator: TargetEnumerator
+execute(context: CommandContext) CommandResult
+validate_params(params: Dict) bool
+get_help_text() str
-_prepare_targets(target_spec: str) List[ScanTarget]
-_execute_scan(targets: List[ScanTarget]) List[ScanResult]
}
class DetectCommandImpl {
-detection_pipeline: DetectionPipeline
-introspector: MCPIntrospector
+execute(context: CommandContext) CommandResult
+validate_params(params: Dict) bool
+get_help_text() str
-_configure_pipeline(params: Dict) PipelineConfig
-_run_detection(pipeline: DetectionPipeline) List[DetectionResult]
}
class ReportCommandImpl {
-report_generator: ReportGenerator
-data_aggregator: DataAggregator
+execute(context: CommandContext) CommandResult
+validate_params(params: Dict) bool
+get_help_text() str
-_load_input_data(file_path: str) ReportData
-_generate_report(data: ReportData, format: str) str
}
class CommandResult {
+success: bool
+data: Dict[str, Any]
+output_content: str
+error_message: str
+execution_time: float
+metadata: Dict[str, Any]
}
class CommandInvoker {
-command_registry: Dict[str, Command]
-context: CommandContext
+register_command(name: str, command: Command)
+execute_command(name: str, params: Dict) CommandResult
+get_available_commands() List[str]
-_prepare_context(params: Dict) CommandContext
}
Command <|-- ScanCommandImpl
Command <|-- DetectCommandImpl
Command <|-- ReportCommandImpl
CommandInvoker --> Command : invokes
CommandInvoker --> CommandContext : uses
Command --> CommandResult : returns
Command --> CommandContext : uses
classDiagram
class ParameterProcessor {
-validators: Dict[str, Validator]
-transformers: Dict[str, Transformer]
+process_parameters(raw_params: Dict) ProcessedParameters
+validate_parameter(name: str, value: Any) bool
+transform_parameter(name: str, value: Any) Any
+get_parameter_help(name: str) str
}
class Validator {
<<interface>>
+validate(value: Any)* bool
+get_error_message()* str
}
class TargetValidator {
+validate(value: Any) bool
+get_error_message() str
-_is_valid_ip(ip_str: str) bool
-_is_valid_cidr(cidr_str: str) bool
-_is_valid_hostname(hostname: str) bool
}
class PortRangeValidator {
+validate(value: Any) bool
+get_error_message() str
-_parse_port_range(range_str: str) List[int]
-_validate_port_number(port: int) bool
}
class FilePathValidator {
+validate(value: Any) bool
+get_error_message() str
-_check_file_exists(path: str) bool
-_check_file_readable(path: str) bool
-_check_directory_writable(path: str) bool
}
class Transformer {
<<interface>>
+transform(value: Any)* Any
+get_transformation_description()* str
}
class PortRangeTransformer {
+transform(value: Any) List[int]
+get_transformation_description() str
-_expand_port_ranges(ranges: str) List[int]
-_resolve_port_aliases(aliases: List[str]) List[int]
}
class TargetEnumerationTransformer {
+transform(value: Any) List[str]
+get_transformation_description() str
-_expand_cidr_range(cidr: str) List[str]
-_resolve_hostname(hostname: str) List[str]
}
class ProcessedParameters {
+validated_params: Dict[str, Any]
+transformation_log: List[str]
+validation_errors: List[str]
+get_parameter(name: str) Any
+has_errors() bool
+get_error_summary() str
}
ParameterProcessor --> Validator : uses
ParameterProcessor --> Transformer : uses
ParameterProcessor --> ProcessedParameters : creates
Validator <|-- TargetValidator
Validator <|-- PortRangeValidator
Validator <|-- FilePathValidator
Transformer <|-- PortRangeTransformer
Transformer <|-- TargetEnumerationTransformer
classDiagram
class ErrorHandler {
-error_strategies: Dict[ErrorType, ErrorStrategy]
+handle_error(error: Exception, context: CommandContext) ErrorResult
+register_strategy(error_type: ErrorType, strategy: ErrorStrategy)
+format_error_message(error: Exception) str
+should_continue_execution(error: Exception) bool
}
class ErrorStrategy {
<<interface>>
+handle(error: Exception, context: CommandContext)* ErrorResult
+can_handle(error: Exception)* bool
+get_recovery_suggestions()* List[str]
}
class ValidationErrorStrategy {
+handle(error: Exception, context: CommandContext) ErrorResult
+can_handle(error: Exception) bool
+get_recovery_suggestions() List[str]
-_extract_parameter_info(error: ValidationError) Dict
}
class NetworkErrorStrategy {
+handle(error: Exception, context: CommandContext) ErrorResult
+can_handle(error: Exception) bool
+get_recovery_suggestions() List[str]
-_diagnose_network_issue(error: NetworkError) NetworkDiagnosis
}
class FileSystemErrorStrategy {
+handle(error: Exception, context: CommandContext) ErrorResult
+can_handle(error: Exception) bool
+get_recovery_suggestions() List[str]
-_check_permissions(file_path: str) PermissionInfo
}
class UnknownErrorStrategy {
+handle(error: Exception, context: CommandContext) ErrorResult
+can_handle(error: Exception) bool
+get_recovery_suggestions() List[str]
-_generate_debug_info(error: Exception) str
}
class ErrorResult {
+handled: bool
+continue_execution: bool
+error_message: str
+recovery_suggestions: List[str]
+debug_information: str
+exit_code: int
}
class ErrorType {
<<enumeration>>
VALIDATION_ERROR
NETWORK_ERROR
FILESYSTEM_ERROR
PERMISSION_ERROR
TIMEOUT_ERROR
UNKNOWN_ERROR
}
ErrorHandler --> ErrorStrategy : uses
ErrorHandler --> ErrorType : categorizes
ErrorHandler --> ErrorResult : creates
ErrorStrategy <|-- ValidationErrorStrategy
ErrorStrategy <|-- NetworkErrorStrategy
ErrorStrategy <|-- FileSystemErrorStrategy
ErrorStrategy <|-- UnknownErrorStrategy
Main CLI Group:
@click.group(cls=HawkEyeGroup, context_settings={"help_option_names": ["-h", "--help"]})
@click.option("--verbose", "-v", is_flag=True, help="Enable verbose output")
@click.option("--quiet", "-q", is_flag=True, help="Suppress all output except errors")
@click.option("--log-file", type=click.Path(), help="Write logs to specified file")
@click.option("--config-file", type=click.Path(exists=True), help="Load configuration from file")
@click.version_option(version=__version__, prog_name="HawkEye")
@click.pass_context
def cli(ctx, verbose: bool, quiet: bool, log_file: Optional[str], config_file: Optional[str]):
"""
🦅 HawkEye - MCP Security Reconnaissance Tool
Identify and assess Model Context Protocol (MCP) server deployments
within network infrastructure for security vulnerabilities.
"""
# Initialize context object
ctx.ensure_object(HawkEyeContext)
# Setup logging and configuration
ctx.obj.setup_logging(verbose=verbose, quiet=quiet, log_file=log_file)
# Load configuration if specified
if config_file:
settings = load_config_from_file(config_file)
ctx.obj.settings = settingsCommand Group Implementation:
@click.group()
def detect():
"""MCP-specific detection and analysis operations."""
pass
@detect.command()
@click.option("--target", "-t", required=True, help="Target specification")
@click.option("--enable-introspection/--disable-introspection", default=True)
@click.option("--timeout", type=int, default=180, help="Timeout in seconds")
@click.option("--output", "-o", type=click.Path(), help="Output file path")
@click.option("--format", "-f", type=click.Choice(["json", "csv", "xml", "html"]), default="json")
@click.pass_context
def mcp(ctx, target: str, enable_introspection: bool, timeout: int,
output: Optional[str], format: str):
"""Detect MCP servers on specified targets."""
logger = ctx.obj.logger
settings = ctx.obj.settings
# Command implementation
try:
# Create and configure detection pipeline
pipeline_config = PipelineConfig(
enable_mcp_introspection=enable_introspection,
introspection_timeout=timeout
)
pipeline = create_detection_pipeline(pipeline_config, settings)
# Execute detection
with console.status("Detecting MCP servers..."):
results = pipeline.execute_pipeline(target)
# Process and output results
if output:
save_results(results, output, format)
else:
display_results(results)
except Exception as e:
logger.error(f"Detection failed: {e}")
raise click.ClickException(f"Detection failed: {e}")Enhanced Command Group:
class HawkEyeGroup(click.Group):
"""Custom Click group with enhanced error handling and help formatting."""
def format_help(self, ctx, formatter):
"""Format help with HawkEye branding."""
formatter.write_heading("🦅 HawkEye - MCP Security Reconnaissance Tool")
formatter.write_paragraph()
formatter.write("Hidden Application Weaknesses & Key Entry-point Yielding Evaluator")
formatter.write_paragraph()
super().format_help(ctx, formatter)
def invoke(self, ctx):
"""Invoke command with comprehensive error handling."""
try:
return super().invoke(ctx)
except HawkEyeError as e:
console.print(f"[red]Error:[/red] {e.message}")
if e.details and ctx.obj.verbose:
console.print(f"[yellow]Details:[/yellow] {e.details}")
sys.exit(1)
except Exception as e:
if ctx.obj.verbose:
console.print_exception()
else:
console.print(f"[red]Unexpected error:[/red] {e}")
sys.exit(1)Target Validation Command:
def validate_target(target: str) -> None:
"""Comprehensive target validation."""
try:
# Try parsing as IP address
ipaddress.ip_address(target)
return
except ValueError:
pass
try:
# Try parsing as CIDR network
ipaddress.ip_network(target, strict=False)
return
except ValueError:
pass
# Check if it's a valid hostname
if target and all(c.isalnum() or c in '.-_' for c in target):
return
raise click.BadParameter(f"Invalid target specification: {target}")
def parse_ports(ports_str: str) -> List[int]:
"""Parse port specification with validation."""
if not ports_str:
return []
port_list = []
for part in ports_str.split(','):
part = part.strip()
if '-' in part:
# Handle port ranges
try:
start, end = part.split('-', 1)
start_port = int(start.strip())
end_port = int(end.strip())
if start_port < 1 or end_port > 65535 or start_port > end_port:
raise ValueError("Invalid port range")
port_list.extend(range(start_port, end_port + 1))
except ValueError:
raise click.BadParameter(f"Invalid port range: {part}")
else:
# Handle single ports
try:
port = int(part)
if port < 1 or port > 65535:
raise ValueError("Port out of range")
port_list.append(port)
except ValueError:
raise click.BadParameter(f"Invalid port: {part}")
return sorted(list(set(port_list)))
# Usage in command definition
@click.command()
@click.option(
"--target", "-t",
required=True,
callback=lambda ctx, param, value: validate_target(value) or value,
help="Target IP address, CIDR range, or hostname"
)
@click.option(
"--ports", "-p",
default="auto",
help="Port range or comma-separated ports"
)
def scan(ctx, target: str, ports: str):
"""Network scanning command with validation."""
# Process parameters
if ports == "auto":
port_list = ctx.obj.settings.scan.default_ports
else:
port_list = parse_ports(ports)
# Execute scan command logic
# ...Command Context Implementation:
class HawkEyeContext:
"""Context object for sharing state between CLI commands."""
def __init__(self):
self.settings = get_settings()
self.logger = None
self.verbose = False
self.quiet = False
self.output_file = None
def setup_logging(self, verbose: bool = False, quiet: bool = False,
log_file: Optional[str] = None):
"""Setup logging configuration based on CLI options."""
self.verbose = verbose
self.quiet = quiet
# Override settings based on CLI options
if verbose:
self.settings.logging.log_level = "DEBUG"
self.settings.logging.console_log_level = "DEBUG"
elif quiet:
self.settings.logging.console_log_level = "ERROR"
if log_file:
self.settings.logging.log_file = Path(log_file)
self.logger = configure_logging()
def get_output_formatter(self, format_type: str):
"""Get appropriate output formatter for the specified format."""
formatters = {
"json": JSONFormatter(),
"csv": CSVFormatter(),
"html": HTMLFormatter(),
"xml": XMLFormatter()
}
return formatters.get(format_type, JSONFormatter())Command Registration:
# Main CLI setup with command registration
cli.add_command(scan)
cli.add_command(detect)
cli.add_command(analyze_threats)
cli.add_command(report)
cli.add_command(config)
# Dynamic command loading example
def load_plugin_commands(cli_group):
"""Dynamically load plugin commands."""
plugin_dir = Path("plugins/commands")
if plugin_dir.exists():
for plugin_file in plugin_dir.glob("*.py"):
spec = importlib.util.spec_from_file_location(
plugin_file.stem, plugin_file
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Register commands from plugin
if hasattr(module, 'plugin_commands'):
for cmd_name, cmd_func in module.plugin_commands.items():
cli_group.add_command(cmd_func, name=cmd_name)- Commands encapsulate all information needed to perform an action
- Parameters, validation logic, and execution logic are bundled together
- Commands can be stored, queued, and executed later
- Separates the object that invokes the operation from the object that performs it
- CLI interface is decoupled from business logic implementation
- Enables flexible command composition and chaining
- New commands can be added without modifying existing code
- Plugin architecture supports third-party command extensions
- Command groups provide logical organization
- Commands can store state needed for undo operations
- Command history enables redo functionality
- Batch operations can be reversed as a unit
- Command sequences can be recorded and replayed
- Complex workflows can be automated
- Configuration-driven command execution
@cli.command()
@click.option("--input-from-scan", help="Use scan results as input")
@click.pass_context
def detect_from_scan(ctx, input_from_scan: str):
"""Detect MCP services from previous scan results."""
# Load scan results
scan_data = load_scan_results(input_from_scan)
# Extract open ports
open_ports = [r for r in scan_data if r.state == "open"]
# Execute detection on open ports
detection_pipeline = create_detection_pipeline()
for port_result in open_ports:
target = f"{port_result.host}:{port_result.port}"
detection_results = detection_pipeline.execute_pipeline(target)
# Process results...@cli.command()
@click.option("--command-file", type=click.Path(exists=True),
help="File containing batch commands")
@click.pass_context
def batch(ctx, command_file: str):
"""Execute commands from batch file."""
with open(command_file, 'r') as f:
commands = [line.strip() for line in f if line.strip() and not line.startswith('#')]
for command_line in commands:
try:
# Parse and execute command
args = shlex.split(command_line)
ctx.invoke(cli, args)
except Exception as e:
console.print(f"[red]Batch command failed:[/red] {command_line}")
console.print(f"[red]Error:[/red] {e}")
if not ctx.obj.settings.batch_continue_on_error:
break@cli.command()
@click.option("--profile", help="Configuration profile to use")
@click.option("--save-profile", help="Save current options as profile")
@click.pass_context
def profile_scan(ctx, profile: str, save_profile: str):
"""Execute scan with predefined profile."""
if profile:
# Load profile configuration
profile_config = load_profile(profile)
# Execute scan with profile settings
ctx.invoke(scan, **profile_config)
if save_profile:
# Save current configuration as profile
current_config = extract_current_config(ctx)
save_profile_config(save_profile, current_config)class WellDesignedCommand:
"""Example of well-designed command following best practices."""
def __init__(self, name: str, description: str):
self.name = name
self.description = description
self.parameters = {}
self.validators = []
self.preprocessors = []
self.postprocessors = []
def add_parameter(self, name: str, param_type: click.ParamType,
required: bool = False, default=None, help_text: str = ""):
"""Add parameter with full specification."""
self.parameters[name] = {
'type': param_type,
'required': required,
'default': default,
'help': help_text
}
def add_validator(self, validator: Callable):
"""Add parameter validator."""
self.validators.append(validator)
def execute(self, **kwargs):
"""Execute command with full lifecycle."""
try:
# Pre-processing
for preprocessor in self.preprocessors:
kwargs = preprocessor(kwargs)
# Validation
for validator in self.validators:
validator(kwargs)
# Main execution
result = self._execute_impl(**kwargs)
# Post-processing
for postprocessor in self.postprocessors:
result = postprocessor(result)
return result
except Exception as e:
return self._handle_error(e, kwargs)
def _execute_impl(self, **kwargs):
"""Actual command implementation."""
raise NotImplementedError
def _handle_error(self, error: Exception, context: Dict):
"""Standard error handling."""
return CommandResult(
success=False,
error=str(error),
context=context
)def create_validation_chain(*validators):
"""Create a chain of validators for comprehensive parameter checking."""
def validate(value):
for validator in validators:
if not validator(value):
return False
return True
return validate
# Usage example
target_validator = create_validation_chain(
lambda x: x is not None,
lambda x: isinstance(x, str),
lambda x: len(x) > 0,
validate_target # Custom target validation
)
@click.option("--target", callback=lambda ctx, param, value: target_validator(value) or value)
def command_with_validation(target):
passclass CommandErrorRecovery:
"""Implement error recovery strategies for commands."""
def __init__(self):
self.recovery_strategies = {
ValidationError: self._handle_validation_error,
NetworkError: self._handle_network_error,
FileNotFoundError: self._handle_file_error,
}
def recover_from_error(self, error: Exception, context: Dict) -> Optional[Dict]:
"""Attempt to recover from command errors."""
error_type = type(error)
if error_type in self.recovery_strategies:
return self.recovery_strategies[error_type](error, context)
return None
def _handle_validation_error(self, error: ValidationError, context: Dict):
"""Provide suggested corrections for validation errors."""
suggestions = {
'suggested_corrections': [],
'retry_with_defaults': True,
'alternative_parameters': {}
}
# Analyze error and provide specific suggestions
if 'target' in str(error):
suggestions['suggested_corrections'].append(
"Check target format: IP, CIDR, or hostname"
)
return suggestions- Complex CLI Applications: When building sophisticated command-line tools with multiple operations
- Parameter-Heavy Operations: When commands have complex parameter validation and processing needs
- Batch Processing: When commands need to be queued, scheduled, or executed in batches
- Plugin Architecture: When the application needs to support third-party command extensions
- Undo/Redo Requirements: When operations need to be reversible
- Simple Scripts: For straightforward scripts with minimal user interaction
- Library APIs: When building programmatic APIs rather than user interfaces
- Real-time Systems: When command overhead might impact performance
- Embedded Systems: When memory constraints make command objects impractical
The Command Pattern implementation in HawkEye's CLI provides a robust, extensible, and user-friendly interface for complex security operations. Key advantages include:
- Modular Command Organization: Clear separation of concerns with dedicated command modules
- Comprehensive Parameter Handling: Robust validation, transformation, and error recovery
- Context Management: Shared state management across command invocations
- Error Handling: Consistent, informative error reporting with recovery suggestions
- Extensibility: Plugin architecture supporting third-party command extensions
The pattern's success in HawkEye demonstrates its value for building professional-grade CLI applications that need to handle complex operations while maintaining usability and reliability.