claude-mpm 3.1.3__py3-none-any.whl → 3.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/__init__.py +3 -3
 - claude_mpm/__main__.py +0 -17
 - claude_mpm/agents/INSTRUCTIONS.md +81 -18
 - claude_mpm/agents/backups/INSTRUCTIONS.md +238 -0
 - claude_mpm/agents/base_agent.json +1 -1
 - claude_mpm/agents/templates/pm.json +25 -0
 - claude_mpm/agents/templates/research.json +2 -1
 - claude_mpm/cli/__init__.py +19 -23
 - claude_mpm/cli/commands/__init__.py +3 -1
 - claude_mpm/cli/commands/agents.py +7 -18
 - claude_mpm/cli/commands/info.py +5 -10
 - claude_mpm/cli/commands/memory.py +232 -0
 - claude_mpm/cli/commands/run.py +501 -28
 - claude_mpm/cli/commands/tickets.py +10 -17
 - claude_mpm/cli/commands/ui.py +15 -37
 - claude_mpm/cli/parser.py +91 -1
 - claude_mpm/cli/utils.py +9 -28
 - claude_mpm/config/socketio_config.py +256 -0
 - claude_mpm/constants.py +9 -0
 - claude_mpm/core/__init__.py +2 -2
 - claude_mpm/core/agent_registry.py +4 -4
 - claude_mpm/core/claude_runner.py +919 -0
 - claude_mpm/core/config.py +21 -1
 - claude_mpm/core/factories.py +1 -1
 - claude_mpm/core/hook_manager.py +196 -0
 - claude_mpm/core/pm_hook_interceptor.py +205 -0
 - claude_mpm/core/service_registry.py +1 -1
 - claude_mpm/core/simple_runner.py +323 -33
 - claude_mpm/core/socketio_pool.py +582 -0
 - claude_mpm/core/websocket_handler.py +233 -0
 - claude_mpm/deployment_paths.py +261 -0
 - claude_mpm/hooks/builtin/memory_hooks_example.py +67 -0
 - claude_mpm/hooks/claude_hooks/hook_handler.py +667 -679
 - claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -4
 - claude_mpm/hooks/memory_integration_hook.py +312 -0
 - claude_mpm/models/__init__.py +9 -91
 - claude_mpm/orchestration/__init__.py +1 -1
 - claude_mpm/scripts/claude-mpm-socketio +32 -0
 - claude_mpm/scripts/claude_mpm_monitor.html +567 -0
 - claude_mpm/scripts/install_socketio_server.py +407 -0
 - claude_mpm/scripts/launch_monitor.py +132 -0
 - claude_mpm/scripts/manage_version.py +479 -0
 - claude_mpm/scripts/socketio_daemon.py +181 -0
 - claude_mpm/scripts/socketio_server_manager.py +428 -0
 - claude_mpm/services/__init__.py +5 -0
 - claude_mpm/services/agent_lifecycle_manager.py +76 -25
 - claude_mpm/services/agent_memory_manager.py +684 -0
 - claude_mpm/services/agent_modification_tracker.py +98 -17
 - claude_mpm/services/agent_persistence_service.py +33 -13
 - claude_mpm/services/agent_registry.py +82 -43
 - claude_mpm/services/hook_service.py +362 -0
 - claude_mpm/services/socketio_client_manager.py +474 -0
 - claude_mpm/services/socketio_server.py +698 -0
 - claude_mpm/services/standalone_socketio_server.py +631 -0
 - claude_mpm/services/ticket_manager.py +4 -5
 - claude_mpm/services/{ticket_manager_dependency_injection.py → ticket_manager_di.py} +12 -39
 - claude_mpm/services/{legacy_ticketing_service.py → ticketing_service_original.py} +9 -16
 - claude_mpm/services/version_control/semantic_versioning.py +9 -10
 - claude_mpm/services/websocket_server.py +376 -0
 - claude_mpm/utils/dependency_manager.py +211 -0
 - claude_mpm/utils/import_migration_example.py +80 -0
 - claude_mpm/utils/path_operations.py +0 -20
 - claude_mpm/web/open_dashboard.py +34 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/METADATA +20 -9
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/RECORD +70 -50
 - claude_mpm-3.2.1.dist-info/entry_points.txt +7 -0
 - claude_mpm/cli_old.py +0 -728
 - claude_mpm/models/common.py +0 -41
 - claude_mpm/models/lifecycle.py +0 -97
 - claude_mpm/models/modification.py +0 -126
 - claude_mpm/models/persistence.py +0 -57
 - claude_mpm/models/registry.py +0 -91
 - claude_mpm/security/__init__.py +0 -8
 - claude_mpm/security/bash_validator.py +0 -393
 - claude_mpm-3.1.3.dist-info/entry_points.txt +0 -4
 - /claude_mpm/{cli_enhancements.py → experimental/cli_enhancements.py} +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/WHEEL +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/licenses/LICENSE +0 -0
 - {claude_mpm-3.1.3.dist-info → claude_mpm-3.2.1.dist-info}/top_level.txt +0 -0
 
| 
         @@ -1,393 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            """Bash command security validator for file access restrictions.
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
     | 
    
         
            -
            This module provides comprehensive validation for bash commands to ensure
         
     | 
| 
       4 
     | 
    
         
            -
            agents cannot perform file operations outside their working directory.
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
     | 
    
         
            -
            Security patterns blocked:
         
     | 
| 
       7 
     | 
    
         
            -
            - File writes via redirects (>, >>)
         
     | 
| 
       8 
     | 
    
         
            -
            - File writes via commands (echo/cat > file, cp/mv to external paths)
         
     | 
| 
       9 
     | 
    
         
            -
            - Directory operations outside working directory (mkdir, rmdir)
         
     | 
| 
       10 
     | 
    
         
            -
            - Dangerous command patterns (rm -rf, sudo, etc)
         
     | 
| 
       11 
     | 
    
         
            -
            """
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
            import re
         
     | 
| 
       14 
     | 
    
         
            -
            import shlex
         
     | 
| 
       15 
     | 
    
         
            -
            from pathlib import Path
         
     | 
| 
       16 
     | 
    
         
            -
            from typing import List, Tuple, Optional, Dict, Set
         
     | 
| 
       17 
     | 
    
         
            -
            import logging
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
            logger = logging.getLogger(__name__)
         
     | 
| 
       20 
     | 
    
         
            -
             
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
            class BashSecurityValidator:
         
     | 
| 
       23 
     | 
    
         
            -
                """Validates bash commands for security violations.
         
     | 
| 
       24 
     | 
    
         
            -
                
         
     | 
| 
       25 
     | 
    
         
            -
                WHY: We need to prevent agents from writing outside their working directory
         
     | 
| 
       26 
     | 
    
         
            -
                through bash commands, which bypass the normal tool validation. This class
         
     | 
| 
       27 
     | 
    
         
            -
                parses bash commands and identifies file operations that would violate
         
     | 
| 
       28 
     | 
    
         
            -
                security boundaries.
         
     | 
| 
       29 
     | 
    
         
            -
                
         
     | 
| 
       30 
     | 
    
         
            -
                DESIGN DECISION: We parse commands rather than execute in a sandbox because:
         
     | 
| 
       31 
     | 
    
         
            -
                - Faster validation without subprocess overhead
         
     | 
| 
       32 
     | 
    
         
            -
                - Can provide detailed error messages about violations
         
     | 
| 
       33 
     | 
    
         
            -
                - Prevents any execution of potentially harmful commands
         
     | 
| 
       34 
     | 
    
         
            -
                """
         
     | 
| 
       35 
     | 
    
         
            -
                
         
     | 
| 
       36 
     | 
    
         
            -
                # Commands that can write/modify files
         
     | 
| 
       37 
     | 
    
         
            -
                WRITE_COMMANDS = {
         
     | 
| 
       38 
     | 
    
         
            -
                    'echo', 'cat', 'printf', 'tee', 'sed', 'awk', 'perl', 'python',
         
     | 
| 
       39 
     | 
    
         
            -
                    'python3', 'node', 'ruby', 'sh', 'bash', 'zsh', 'fish'
         
     | 
| 
       40 
     | 
    
         
            -
                }
         
     | 
| 
       41 
     | 
    
         
            -
                
         
     | 
| 
       42 
     | 
    
         
            -
                # Commands that copy/move files
         
     | 
| 
       43 
     | 
    
         
            -
                FILE_TRANSFER_COMMANDS = {
         
     | 
| 
       44 
     | 
    
         
            -
                    'cp', 'mv', 'scp', 'rsync', 'install'
         
     | 
| 
       45 
     | 
    
         
            -
                }
         
     | 
| 
       46 
     | 
    
         
            -
                
         
     | 
| 
       47 
     | 
    
         
            -
                # Commands that create/modify directories
         
     | 
| 
       48 
     | 
    
         
            -
                DIR_COMMANDS = {
         
     | 
| 
       49 
     | 
    
         
            -
                    'mkdir', 'rmdir', 'rm'
         
     | 
| 
       50 
     | 
    
         
            -
                }
         
     | 
| 
       51 
     | 
    
         
            -
                
         
     | 
| 
       52 
     | 
    
         
            -
                # Commands that are always dangerous
         
     | 
| 
       53 
     | 
    
         
            -
                DANGEROUS_COMMANDS = {
         
     | 
| 
       54 
     | 
    
         
            -
                    'sudo', 'su', 'chmod', 'chown', 'chgrp', 'mkfs', 'dd',
         
     | 
| 
       55 
     | 
    
         
            -
                    'format', 'fdisk', 'mount', 'umount'
         
     | 
| 
       56 
     | 
    
         
            -
                }
         
     | 
| 
       57 
     | 
    
         
            -
                
         
     | 
| 
       58 
     | 
    
         
            -
                # Redirect operators that can write files
         
     | 
| 
       59 
     | 
    
         
            -
                REDIRECT_OPERATORS = {'>', '>>', '>&', '&>', '>|'}
         
     | 
| 
       60 
     | 
    
         
            -
                
         
     | 
| 
       61 
     | 
    
         
            -
                def __init__(self, working_dir: Path):
         
     | 
| 
       62 
     | 
    
         
            -
                    """Initialize validator with working directory.
         
     | 
| 
       63 
     | 
    
         
            -
                    
         
     | 
| 
       64 
     | 
    
         
            -
                    Args:
         
     | 
| 
       65 
     | 
    
         
            -
                        working_dir: The directory agents are restricted to
         
     | 
| 
       66 
     | 
    
         
            -
                    """
         
     | 
| 
       67 
     | 
    
         
            -
                    self.working_dir = working_dir.resolve()
         
     | 
| 
       68 
     | 
    
         
            -
                    
         
     | 
| 
       69 
     | 
    
         
            -
                def validate_command(self, command: str) -> Tuple[bool, Optional[str]]:
         
     | 
| 
       70 
     | 
    
         
            -
                    """Validate a bash command for security violations.
         
     | 
| 
       71 
     | 
    
         
            -
                    
         
     | 
| 
       72 
     | 
    
         
            -
                    Args:
         
     | 
| 
       73 
     | 
    
         
            -
                        command: The bash command to validate
         
     | 
| 
       74 
     | 
    
         
            -
                        
         
     | 
| 
       75 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       76 
     | 
    
         
            -
                        Tuple of (is_valid, error_message)
         
     | 
| 
       77 
     | 
    
         
            -
                        - is_valid: True if command is safe, False if it violates security
         
     | 
| 
       78 
     | 
    
         
            -
                        - error_message: Detailed error message if validation fails
         
     | 
| 
       79 
     | 
    
         
            -
                    """
         
     | 
| 
       80 
     | 
    
         
            -
                    try:
         
     | 
| 
       81 
     | 
    
         
            -
                        # Check for dangerous commands first
         
     | 
| 
       82 
     | 
    
         
            -
                        danger_check = self._check_dangerous_commands(command)
         
     | 
| 
       83 
     | 
    
         
            -
                        if danger_check:
         
     | 
| 
       84 
     | 
    
         
            -
                            return False, danger_check
         
     | 
| 
       85 
     | 
    
         
            -
                        
         
     | 
| 
       86 
     | 
    
         
            -
                        # Check for file redirects
         
     | 
| 
       87 
     | 
    
         
            -
                        redirect_check = self._check_redirects(command)
         
     | 
| 
       88 
     | 
    
         
            -
                        if redirect_check:
         
     | 
| 
       89 
     | 
    
         
            -
                            return False, redirect_check
         
     | 
| 
       90 
     | 
    
         
            -
                        
         
     | 
| 
       91 
     | 
    
         
            -
                        # Check for file operations in commands
         
     | 
| 
       92 
     | 
    
         
            -
                        file_op_check = self._check_file_operations(command)
         
     | 
| 
       93 
     | 
    
         
            -
                        if file_op_check:
         
     | 
| 
       94 
     | 
    
         
            -
                            return False, file_op_check
         
     | 
| 
       95 
     | 
    
         
            -
                        
         
     | 
| 
       96 
     | 
    
         
            -
                        # Check for directory operations
         
     | 
| 
       97 
     | 
    
         
            -
                        dir_op_check = self._check_directory_operations(command)
         
     | 
| 
       98 
     | 
    
         
            -
                        if dir_op_check:
         
     | 
| 
       99 
     | 
    
         
            -
                            return False, dir_op_check
         
     | 
| 
       100 
     | 
    
         
            -
                        
         
     | 
| 
       101 
     | 
    
         
            -
                        # Check for pipe operations that could write files
         
     | 
| 
       102 
     | 
    
         
            -
                        pipe_check = self._check_pipe_operations(command)
         
     | 
| 
       103 
     | 
    
         
            -
                        if pipe_check:
         
     | 
| 
       104 
     | 
    
         
            -
                            return False, pipe_check
         
     | 
| 
       105 
     | 
    
         
            -
                        
         
     | 
| 
       106 
     | 
    
         
            -
                        return True, None
         
     | 
| 
       107 
     | 
    
         
            -
                        
         
     | 
| 
       108 
     | 
    
         
            -
                    except Exception as e:
         
     | 
| 
       109 
     | 
    
         
            -
                        logger.error(f"Error validating bash command: {e}")
         
     | 
| 
       110 
     | 
    
         
            -
                        # On error, be conservative and block
         
     | 
| 
       111 
     | 
    
         
            -
                        return False, f"Command validation error: {str(e)}"
         
     | 
| 
       112 
     | 
    
         
            -
                
         
     | 
| 
       113 
     | 
    
         
            -
                def _check_dangerous_commands(self, command: str) -> Optional[str]:
         
     | 
| 
       114 
     | 
    
         
            -
                    """Check for inherently dangerous commands.
         
     | 
| 
       115 
     | 
    
         
            -
                    
         
     | 
| 
       116 
     | 
    
         
            -
                    Args:
         
     | 
| 
       117 
     | 
    
         
            -
                        command: The command to check
         
     | 
| 
       118 
     | 
    
         
            -
                        
         
     | 
| 
       119 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       120 
     | 
    
         
            -
                        Error message if dangerous command found, None otherwise
         
     | 
| 
       121 
     | 
    
         
            -
                    """
         
     | 
| 
       122 
     | 
    
         
            -
                    # Split by common separators to handle command chains
         
     | 
| 
       123 
     | 
    
         
            -
                    parts = re.split(r'[;&|]', command)
         
     | 
| 
       124 
     | 
    
         
            -
                    
         
     | 
| 
       125 
     | 
    
         
            -
                    for part in parts:
         
     | 
| 
       126 
     | 
    
         
            -
                        tokens = part.strip().split()
         
     | 
| 
       127 
     | 
    
         
            -
                        if not tokens:
         
     | 
| 
       128 
     | 
    
         
            -
                            continue
         
     | 
| 
       129 
     | 
    
         
            -
                            
         
     | 
| 
       130 
     | 
    
         
            -
                        cmd = tokens[0]
         
     | 
| 
       131 
     | 
    
         
            -
                        
         
     | 
| 
       132 
     | 
    
         
            -
                        # Check absolute dangerous commands
         
     | 
| 
       133 
     | 
    
         
            -
                        if cmd in self.DANGEROUS_COMMANDS:
         
     | 
| 
       134 
     | 
    
         
            -
                            return (f"Security Policy: Command '{cmd}' is not allowed.\n"
         
     | 
| 
       135 
     | 
    
         
            -
                                   f"This command could compromise system security.")
         
     | 
| 
       136 
     | 
    
         
            -
                        
         
     | 
| 
       137 
     | 
    
         
            -
                        # Check for sudo/su with any command
         
     | 
| 
       138 
     | 
    
         
            -
                        if cmd in ['sudo', 'su'] and len(tokens) > 1:
         
     | 
| 
       139 
     | 
    
         
            -
                            return (f"Security Policy: Privileged command execution is not allowed.\n"
         
     | 
| 
       140 
     | 
    
         
            -
                                   f"Command '{cmd}' cannot be used to escalate privileges.")
         
     | 
| 
       141 
     | 
    
         
            -
                        
         
     | 
| 
       142 
     | 
    
         
            -
                        # Check for rm -rf patterns
         
     | 
| 
       143 
     | 
    
         
            -
                        if cmd == 'rm' and any(arg in tokens for arg in ['-rf', '-fr', '--force']):
         
     | 
| 
       144 
     | 
    
         
            -
                            # Check if targeting root or system directories
         
     | 
| 
       145 
     | 
    
         
            -
                            for token in tokens[1:]:
         
     | 
| 
       146 
     | 
    
         
            -
                                if token.startswith('/') and not token.startswith(str(self.working_dir)):
         
     | 
| 
       147 
     | 
    
         
            -
                                    return (f"Security Policy: Dangerous rm command detected.\n"
         
     | 
| 
       148 
     | 
    
         
            -
                                           f"Cannot remove files outside working directory.")
         
     | 
| 
       149 
     | 
    
         
            -
                    
         
     | 
| 
       150 
     | 
    
         
            -
                    return None
         
     | 
| 
       151 
     | 
    
         
            -
                
         
     | 
| 
       152 
     | 
    
         
            -
                def _check_redirects(self, command: str) -> Optional[str]:
         
     | 
| 
       153 
     | 
    
         
            -
                    """Check for file redirects that write outside working directory.
         
     | 
| 
       154 
     | 
    
         
            -
                    
         
     | 
| 
       155 
     | 
    
         
            -
                    Args:
         
     | 
| 
       156 
     | 
    
         
            -
                        command: The command to check
         
     | 
| 
       157 
     | 
    
         
            -
                        
         
     | 
| 
       158 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       159 
     | 
    
         
            -
                        Error message if unsafe redirect found, None otherwise
         
     | 
| 
       160 
     | 
    
         
            -
                    """
         
     | 
| 
       161 
     | 
    
         
            -
                    # Pattern to find redirects: > or >> followed by a file path
         
     | 
| 
       162 
     | 
    
         
            -
                    # Handles cases like: echo "test" > /etc/passwd
         
     | 
| 
       163 
     | 
    
         
            -
                    redirect_pattern = r'(' + '|'.join(re.escape(op) for op in self.REDIRECT_OPERATORS) + r')\s*([\'"]?)([^\s\'"]+)\2'
         
     | 
| 
       164 
     | 
    
         
            -
                    
         
     | 
| 
       165 
     | 
    
         
            -
                    for match in re.finditer(redirect_pattern, command):
         
     | 
| 
       166 
     | 
    
         
            -
                        operator = match.group(1)
         
     | 
| 
       167 
     | 
    
         
            -
                        file_path = match.group(3)
         
     | 
| 
       168 
     | 
    
         
            -
                        
         
     | 
| 
       169 
     | 
    
         
            -
                        # Skip descriptors like >&2
         
     | 
| 
       170 
     | 
    
         
            -
                        if file_path.isdigit():
         
     | 
| 
       171 
     | 
    
         
            -
                            continue
         
     | 
| 
       172 
     | 
    
         
            -
                        
         
     | 
| 
       173 
     | 
    
         
            -
                        # Validate the target path
         
     | 
| 
       174 
     | 
    
         
            -
                        validation = self._validate_path(file_path)
         
     | 
| 
       175 
     | 
    
         
            -
                        if not validation[0]:
         
     | 
| 
       176 
     | 
    
         
            -
                            return (f"Security Policy: File redirect outside working directory not allowed.\n"
         
     | 
| 
       177 
     | 
    
         
            -
                                   f"Redirect operator '{operator}' targeting: {file_path}\n"
         
     | 
| 
       178 
     | 
    
         
            -
                                   f"{validation[1]}")
         
     | 
| 
       179 
     | 
    
         
            -
                    
         
     | 
| 
       180 
     | 
    
         
            -
                    # Also check for here-documents that might write files
         
     | 
| 
       181 
     | 
    
         
            -
                    if '<<' in command:
         
     | 
| 
       182 
     | 
    
         
            -
                        # Check if it's followed by a file write
         
     | 
| 
       183 
     | 
    
         
            -
                        heredoc_write = re.search(r'<<.*?\|.*?>\s*([^\s]+)', command)
         
     | 
| 
       184 
     | 
    
         
            -
                        if heredoc_write:
         
     | 
| 
       185 
     | 
    
         
            -
                            file_path = heredoc_write.group(1)
         
     | 
| 
       186 
     | 
    
         
            -
                            validation = self._validate_path(file_path)
         
     | 
| 
       187 
     | 
    
         
            -
                            if not validation[0]:
         
     | 
| 
       188 
     | 
    
         
            -
                                return (f"Security Policy: Here-document redirect outside working directory.\n"
         
     | 
| 
       189 
     | 
    
         
            -
                                       f"Target file: {file_path}")
         
     | 
| 
       190 
     | 
    
         
            -
                    
         
     | 
| 
       191 
     | 
    
         
            -
                    return None
         
     | 
| 
       192 
     | 
    
         
            -
                
         
     | 
| 
       193 
     | 
    
         
            -
                def _check_file_operations(self, command: str) -> Optional[str]:
         
     | 
| 
       194 
     | 
    
         
            -
                    """Check for file operations that write outside working directory.
         
     | 
| 
       195 
     | 
    
         
            -
                    
         
     | 
| 
       196 
     | 
    
         
            -
                    Args:
         
     | 
| 
       197 
     | 
    
         
            -
                        command: The command to check
         
     | 
| 
       198 
     | 
    
         
            -
                        
         
     | 
| 
       199 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       200 
     | 
    
         
            -
                        Error message if unsafe file operation found, None otherwise
         
     | 
| 
       201 
     | 
    
         
            -
                    """
         
     | 
| 
       202 
     | 
    
         
            -
                    try:
         
     | 
| 
       203 
     | 
    
         
            -
                        # Parse command to handle quotes properly
         
     | 
| 
       204 
     | 
    
         
            -
                        tokens = shlex.split(command)
         
     | 
| 
       205 
     | 
    
         
            -
                    except ValueError:
         
     | 
| 
       206 
     | 
    
         
            -
                        # If shlex fails, fall back to simple split
         
     | 
| 
       207 
     | 
    
         
            -
                        tokens = command.split()
         
     | 
| 
       208 
     | 
    
         
            -
                    
         
     | 
| 
       209 
     | 
    
         
            -
                    i = 0
         
     | 
| 
       210 
     | 
    
         
            -
                    while i < len(tokens):
         
     | 
| 
       211 
     | 
    
         
            -
                        token = tokens[i]
         
     | 
| 
       212 
     | 
    
         
            -
                        
         
     | 
| 
       213 
     | 
    
         
            -
                        # Check copy/move commands
         
     | 
| 
       214 
     | 
    
         
            -
                        if token in self.FILE_TRANSFER_COMMANDS:
         
     | 
| 
       215 
     | 
    
         
            -
                            # These commands typically have source and destination
         
     | 
| 
       216 
     | 
    
         
            -
                            # We need to check the destination
         
     | 
| 
       217 
     | 
    
         
            -
                            dest_idx = None
         
     | 
| 
       218 
     | 
    
         
            -
                            
         
     | 
| 
       219 
     | 
    
         
            -
                            if token in ['cp', 'mv', 'install']:
         
     | 
| 
       220 
     | 
    
         
            -
                                # Skip options
         
     | 
| 
       221 
     | 
    
         
            -
                                j = i + 1
         
     | 
| 
       222 
     | 
    
         
            -
                                while j < len(tokens) and tokens[j].startswith('-'):
         
     | 
| 
       223 
     | 
    
         
            -
                                    j += 1
         
     | 
| 
       224 
     | 
    
         
            -
                                # Last argument is typically destination
         
     | 
| 
       225 
     | 
    
         
            -
                                if j < len(tokens):
         
     | 
| 
       226 
     | 
    
         
            -
                                    dest_idx = len(tokens) - 1
         
     | 
| 
       227 
     | 
    
         
            -
                            
         
     | 
| 
       228 
     | 
    
         
            -
                            elif token == 'rsync':
         
     | 
| 
       229 
     | 
    
         
            -
                                # rsync can have complex syntax, look for paths
         
     | 
| 
       230 
     | 
    
         
            -
                                for j in range(i + 1, len(tokens)):
         
     | 
| 
       231 
     | 
    
         
            -
                                    if not tokens[j].startswith('-') and ':' not in tokens[j]:
         
     | 
| 
       232 
     | 
    
         
            -
                                        # Potential local path
         
     | 
| 
       233 
     | 
    
         
            -
                                        validation = self._validate_path(tokens[j])
         
     | 
| 
       234 
     | 
    
         
            -
                                        if not validation[0]:
         
     | 
| 
       235 
     | 
    
         
            -
                                            return (f"Security Policy: {token} operation outside working directory.\n"
         
     | 
| 
       236 
     | 
    
         
            -
                                                   f"Target path: {tokens[j]}\n{validation[1]}")
         
     | 
| 
       237 
     | 
    
         
            -
                            
         
     | 
| 
       238 
     | 
    
         
            -
                            if dest_idx and dest_idx < len(tokens):
         
     | 
| 
       239 
     | 
    
         
            -
                                dest_path = tokens[dest_idx]
         
     | 
| 
       240 
     | 
    
         
            -
                                validation = self._validate_path(dest_path)
         
     | 
| 
       241 
     | 
    
         
            -
                                if not validation[0]:
         
     | 
| 
       242 
     | 
    
         
            -
                                    return (f"Security Policy: {token} destination outside working directory.\n"
         
     | 
| 
       243 
     | 
    
         
            -
                                           f"Destination: {dest_path}\n{validation[1]}")
         
     | 
| 
       244 
     | 
    
         
            -
                        
         
     | 
| 
       245 
     | 
    
         
            -
                        # Check for write operations via command substitution
         
     | 
| 
       246 
     | 
    
         
            -
                        if token in self.WRITE_COMMANDS and i + 1 < len(tokens):
         
     | 
| 
       247 
     | 
    
         
            -
                            # Look for patterns like: echo "data" > file
         
     | 
| 
       248 
     | 
    
         
            -
                            for j in range(i + 1, len(tokens)):
         
     | 
| 
       249 
     | 
    
         
            -
                                if tokens[j] in self.REDIRECT_OPERATORS and j + 1 < len(tokens):
         
     | 
| 
       250 
     | 
    
         
            -
                                    file_path = tokens[j + 1]
         
     | 
| 
       251 
     | 
    
         
            -
                                    validation = self._validate_path(file_path)
         
     | 
| 
       252 
     | 
    
         
            -
                                    if not validation[0]:
         
     | 
| 
       253 
     | 
    
         
            -
                                        return (f"Security Policy: {token} write outside working directory.\n"
         
     | 
| 
       254 
     | 
    
         
            -
                                               f"Target file: {file_path}\n{validation[1]}")
         
     | 
| 
       255 
     | 
    
         
            -
                        
         
     | 
| 
       256 
     | 
    
         
            -
                        i += 1
         
     | 
| 
       257 
     | 
    
         
            -
                    
         
     | 
| 
       258 
     | 
    
         
            -
                    return None
         
     | 
| 
       259 
     | 
    
         
            -
                
         
     | 
| 
       260 
     | 
    
         
            -
                def _check_directory_operations(self, command: str) -> Optional[str]:
         
     | 
| 
       261 
     | 
    
         
            -
                    """Check for directory operations outside working directory.
         
     | 
| 
       262 
     | 
    
         
            -
                    
         
     | 
| 
       263 
     | 
    
         
            -
                    Args:
         
     | 
| 
       264 
     | 
    
         
            -
                        command: The command to check
         
     | 
| 
       265 
     | 
    
         
            -
                        
         
     | 
| 
       266 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       267 
     | 
    
         
            -
                        Error message if unsafe directory operation found, None otherwise
         
     | 
| 
       268 
     | 
    
         
            -
                    """
         
     | 
| 
       269 
     | 
    
         
            -
                    try:
         
     | 
| 
       270 
     | 
    
         
            -
                        tokens = shlex.split(command)
         
     | 
| 
       271 
     | 
    
         
            -
                    except ValueError:
         
     | 
| 
       272 
     | 
    
         
            -
                        tokens = command.split()
         
     | 
| 
       273 
     | 
    
         
            -
                    
         
     | 
| 
       274 
     | 
    
         
            -
                    i = 0
         
     | 
| 
       275 
     | 
    
         
            -
                    while i < len(tokens):
         
     | 
| 
       276 
     | 
    
         
            -
                        token = tokens[i]
         
     | 
| 
       277 
     | 
    
         
            -
                        
         
     | 
| 
       278 
     | 
    
         
            -
                        if token in self.DIR_COMMANDS:
         
     | 
| 
       279 
     | 
    
         
            -
                            # Check all arguments after the command
         
     | 
| 
       280 
     | 
    
         
            -
                            for j in range(i + 1, len(tokens)):
         
     | 
| 
       281 
     | 
    
         
            -
                                arg = tokens[j]
         
     | 
| 
       282 
     | 
    
         
            -
                                # Skip options
         
     | 
| 
       283 
     | 
    
         
            -
                                if arg.startswith('-'):
         
     | 
| 
       284 
     | 
    
         
            -
                                    continue
         
     | 
| 
       285 
     | 
    
         
            -
                                
         
     | 
| 
       286 
     | 
    
         
            -
                                # Validate the path
         
     | 
| 
       287 
     | 
    
         
            -
                                validation = self._validate_path(arg)
         
     | 
| 
       288 
     | 
    
         
            -
                                if not validation[0]:
         
     | 
| 
       289 
     | 
    
         
            -
                                    return (f"Security Policy: {token} operation outside working directory.\n"
         
     | 
| 
       290 
     | 
    
         
            -
                                           f"Target path: {arg}\n{validation[1]}")
         
     | 
| 
       291 
     | 
    
         
            -
                        
         
     | 
| 
       292 
     | 
    
         
            -
                        i += 1
         
     | 
| 
       293 
     | 
    
         
            -
                    
         
     | 
| 
       294 
     | 
    
         
            -
                    return None
         
     | 
| 
       295 
     | 
    
         
            -
                
         
     | 
| 
       296 
     | 
    
         
            -
                def _check_pipe_operations(self, command: str) -> Optional[str]:
         
     | 
| 
       297 
     | 
    
         
            -
                    """Check for pipe operations that could write files.
         
     | 
| 
       298 
     | 
    
         
            -
                    
         
     | 
| 
       299 
     | 
    
         
            -
                    Args:
         
     | 
| 
       300 
     | 
    
         
            -
                        command: The command to check
         
     | 
| 
       301 
     | 
    
         
            -
                        
         
     | 
| 
       302 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       303 
     | 
    
         
            -
                        Error message if unsafe pipe operation found, None otherwise
         
     | 
| 
       304 
     | 
    
         
            -
                    """
         
     | 
| 
       305 
     | 
    
         
            -
                    # Check for tee command which can write to files
         
     | 
| 
       306 
     | 
    
         
            -
                    if 'tee' in command:
         
     | 
| 
       307 
     | 
    
         
            -
                        tee_pattern = r'tee\s+(?:-[a-zA-Z]+\s+)*([^\s|]+)'
         
     | 
| 
       308 
     | 
    
         
            -
                        for match in re.finditer(tee_pattern, command):
         
     | 
| 
       309 
     | 
    
         
            -
                            file_path = match.group(1)
         
     | 
| 
       310 
     | 
    
         
            -
                            validation = self._validate_path(file_path)
         
     | 
| 
       311 
     | 
    
         
            -
                            if not validation[0]:
         
     | 
| 
       312 
     | 
    
         
            -
                                return (f"Security Policy: tee write outside working directory.\n"
         
     | 
| 
       313 
     | 
    
         
            -
                                       f"Target file: {file_path}\n{validation[1]}")
         
     | 
| 
       314 
     | 
    
         
            -
                    
         
     | 
| 
       315 
     | 
    
         
            -
                    # Check for dd command which can write to files/devices
         
     | 
| 
       316 
     | 
    
         
            -
                    if 'dd' in command:
         
     | 
| 
       317 
     | 
    
         
            -
                        dd_pattern = r'of=([^\s]+)'
         
     | 
| 
       318 
     | 
    
         
            -
                        match = re.search(dd_pattern, command)
         
     | 
| 
       319 
     | 
    
         
            -
                        if match:
         
     | 
| 
       320 
     | 
    
         
            -
                            file_path = match.group(1)
         
     | 
| 
       321 
     | 
    
         
            -
                            validation = self._validate_path(file_path)
         
     | 
| 
       322 
     | 
    
         
            -
                            if not validation[0]:
         
     | 
| 
       323 
     | 
    
         
            -
                                return (f"Security Policy: dd write outside working directory.\n"
         
     | 
| 
       324 
     | 
    
         
            -
                                       f"Target file: {file_path}\n{validation[1]}")
         
     | 
| 
       325 
     | 
    
         
            -
                    
         
     | 
| 
       326 
     | 
    
         
            -
                    return None
         
     | 
| 
       327 
     | 
    
         
            -
                
         
     | 
| 
       328 
     | 
    
         
            -
                def _validate_path(self, path_str: str) -> Tuple[bool, str]:
         
     | 
| 
       329 
     | 
    
         
            -
                    """Validate a path is within working directory.
         
     | 
| 
       330 
     | 
    
         
            -
                    
         
     | 
| 
       331 
     | 
    
         
            -
                    Args:
         
     | 
| 
       332 
     | 
    
         
            -
                        path_str: The path string to validate
         
     | 
| 
       333 
     | 
    
         
            -
                        
         
     | 
| 
       334 
     | 
    
         
            -
                    Returns:
         
     | 
| 
       335 
     | 
    
         
            -
                        Tuple of (is_valid, message)
         
     | 
| 
       336 
     | 
    
         
            -
                    """
         
     | 
| 
       337 
     | 
    
         
            -
                    # Handle empty or special paths
         
     | 
| 
       338 
     | 
    
         
            -
                    if not path_str or path_str in ['-', '/dev/null', '/dev/stdout', '/dev/stderr']:
         
     | 
| 
       339 
     | 
    
         
            -
                        return True, ""
         
     | 
| 
       340 
     | 
    
         
            -
                    
         
     | 
| 
       341 
     | 
    
         
            -
                    # Remove quotes if present
         
     | 
| 
       342 
     | 
    
         
            -
                    path_str = path_str.strip('\'"')
         
     | 
| 
       343 
     | 
    
         
            -
                    
         
     | 
| 
       344 
     | 
    
         
            -
                    # Check for environment variables that typically point outside working directory
         
     | 
| 
       345 
     | 
    
         
            -
                    # Common patterns: $HOME, ${HOME}, $USER, ${USER}, etc.
         
     | 
| 
       346 
     | 
    
         
            -
                    env_patterns = [
         
     | 
| 
       347 
     | 
    
         
            -
                        r'\$HOME', r'\${HOME}', r'~/', 
         
     | 
| 
       348 
     | 
    
         
            -
                        r'\$USER', r'\${USER}',
         
     | 
| 
       349 
     | 
    
         
            -
                        r'\$TMPDIR', r'\${TMPDIR}',
         
     | 
| 
       350 
     | 
    
         
            -
                        r'/tmp/', r'/var/', r'/etc/', r'/usr/', r'/opt/',
         
     | 
| 
       351 
     | 
    
         
            -
                        r'/System/', r'/Library/', r'/Applications/',  # macOS
         
     | 
| 
       352 
     | 
    
         
            -
                        r'C:\\', r'D:\\',  # Windows
         
     | 
| 
       353 
     | 
    
         
            -
                    ]
         
     | 
| 
       354 
     | 
    
         
            -
                    
         
     | 
| 
       355 
     | 
    
         
            -
                    for pattern in env_patterns:
         
     | 
| 
       356 
     | 
    
         
            -
                        if re.search(pattern, path_str, re.IGNORECASE):
         
     | 
| 
       357 
     | 
    
         
            -
                            return False, (f"Security Policy: Path '{path_str}' contains environment variable "
         
     | 
| 
       358 
     | 
    
         
            -
                                         f"or system path that likely points outside working directory.\n"
         
     | 
| 
       359 
     | 
    
         
            -
                                         f"Please use relative paths or absolute paths within '{self.working_dir}'")
         
     | 
| 
       360 
     | 
    
         
            -
                    
         
     | 
| 
       361 
     | 
    
         
            -
                    try:
         
     | 
| 
       362 
     | 
    
         
            -
                        # Convert to Path object
         
     | 
| 
       363 
     | 
    
         
            -
                        if path_str.startswith('/'):
         
     | 
| 
       364 
     | 
    
         
            -
                            # Absolute path
         
     | 
| 
       365 
     | 
    
         
            -
                            path = Path(path_str).resolve()
         
     | 
| 
       366 
     | 
    
         
            -
                        else:
         
     | 
| 
       367 
     | 
    
         
            -
                            # Relative path - resolve relative to working directory
         
     | 
| 
       368 
     | 
    
         
            -
                            path = (self.working_dir / path_str).resolve()
         
     | 
| 
       369 
     | 
    
         
            -
                        
         
     | 
| 
       370 
     | 
    
         
            -
                        # Check if path is within working directory
         
     | 
| 
       371 
     | 
    
         
            -
                        try:
         
     | 
| 
       372 
     | 
    
         
            -
                            path.relative_to(self.working_dir)
         
     | 
| 
       373 
     | 
    
         
            -
                            return True, ""
         
     | 
| 
       374 
     | 
    
         
            -
                        except ValueError:
         
     | 
| 
       375 
     | 
    
         
            -
                            # Path is outside working directory
         
     | 
| 
       376 
     | 
    
         
            -
                            return False, (f"Path '{path_str}' resolves to '{path}' which is outside "
         
     | 
| 
       377 
     | 
    
         
            -
                                         f"the working directory '{self.working_dir}'")
         
     | 
| 
       378 
     | 
    
         
            -
                            
         
     | 
| 
       379 
     | 
    
         
            -
                    except Exception as e:
         
     | 
| 
       380 
     | 
    
         
            -
                        # If we can't resolve the path, be conservative and block
         
     | 
| 
       381 
     | 
    
         
            -
                        return False, f"Cannot validate path '{path_str}': {str(e)}"
         
     | 
| 
       382 
     | 
    
         
            -
             
     | 
| 
       383 
     | 
    
         
            -
             
     | 
| 
       384 
     | 
    
         
            -
            def create_validator(working_dir: Path) -> BashSecurityValidator:
         
     | 
| 
       385 
     | 
    
         
            -
                """Factory function to create a bash security validator.
         
     | 
| 
       386 
     | 
    
         
            -
                
         
     | 
| 
       387 
     | 
    
         
            -
                Args:
         
     | 
| 
       388 
     | 
    
         
            -
                    working_dir: The working directory to restrict operations to
         
     | 
| 
       389 
     | 
    
         
            -
                    
         
     | 
| 
       390 
     | 
    
         
            -
                Returns:
         
     | 
| 
       391 
     | 
    
         
            -
                    Configured BashSecurityValidator instance
         
     | 
| 
       392 
     | 
    
         
            -
                """
         
     | 
| 
       393 
     | 
    
         
            -
                return BashSecurityValidator(working_dir)
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     | 
| 
         
            File without changes
         
     |