| 19 | |
| 20 | |
| 21 | class BashTool(Tool): |
| 22 | DANGEROUS_PATTERNS = [ |
| 23 | "rm -rf /", "rm -rf ~", "sudo rm", |
| 24 | "git push --force", "git reset --hard", |
| 25 | "> /dev/sda", "mkfs", "dd if=", |
| 26 | ":(){ :|:& };:", |
| 27 | ] |
| 28 | |
| 29 | @property |
| 30 | def name(self) -> str: |
| 31 | return "bash" |
| 32 | |
| 33 | @property |
| 34 | def description(self) -> str: |
| 35 | return ( |
| 36 | "Execute a bash command. Use for running scripts, installing packages, " |
| 37 | "git operations, and any shell task. Commands run in the current working directory." |
| 38 | ) |
| 39 | |
| 40 | @property |
| 41 | def input_schema(self) -> dict[str, Any]: |
| 42 | return { |
| 43 | "type": "object", |
| 44 | "properties": { |
| 45 | "command": { |
| 46 | "type": "string", |
| 47 | "description": "The bash command to execute.", |
| 48 | }, |
| 49 | }, |
| 50 | "required": ["command"], |
| 51 | } |
| 52 | |
| 53 | def check_permissions(self, params: dict[str, Any]) -> str | None: |
| 54 | cmd = params.get("command", "") |
| 55 | for pattern in self.DANGEROUS_PATTERNS: |
| 56 | if pattern in cmd: |
| 57 | return f"Blocked: command matches dangerous pattern '{pattern}'" |
| 58 | return None |
| 59 | |
| 60 | def execute(self, params: dict[str, Any]) -> ToolResult: |
| 61 | command = params.get("command", "") |
| 62 | if not command.strip(): |
| 63 | return ToolResult(output="Error: empty command", is_error=True) |
| 64 | try: |
| 65 | result = subprocess.run( |
| 66 | command, |
| 67 | shell=True, |
| 68 | capture_output=True, |
| 69 | text=True, |
| 70 | timeout=120, |
| 71 | cwd=None, |
| 72 | ) |
| 73 | output_parts = [] |
| 74 | if result.stdout: |
| 75 | output_parts.append(result.stdout) |
| 76 | if result.stderr: |
| 77 | output_parts.append(f"STDERR:\n{result.stderr}") |
| 78 | output = "\n".join(output_parts) or "(no output)" |
no outgoing calls