-
Notifications
You must be signed in to change notification settings - Fork 628
Expand file tree
/
Copy pathpre_tool_use.py
More file actions
executable file
·123 lines (101 loc) · 4.44 KB
/
Copy pathpre_tool_use.py
File metadata and controls
executable file
·123 lines (101 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.8"
# ///
import json
import sys
import re
from pathlib import Path
from _log_common import append_log_data
def is_dangerous_rm_command(command):
"""
Comprehensive detection of dangerous rm commands.
Matches various forms of rm -rf and similar destructive patterns.
"""
# Normalize command by removing extra spaces and converting to lowercase
normalized = ' '.join(command.lower().split())
# Pattern 1: Standard rm -rf variations
patterns = [
r'\brm\s+.*-[a-z]*r[a-z]*f', # rm -rf, rm -fr, rm -Rf, etc.
r'\brm\s+.*-[a-z]*f[a-z]*r', # rm -fr variations
r'\brm\s+--recursive\s+--force', # rm --recursive --force
r'\brm\s+--force\s+--recursive', # rm --force --recursive
r'\brm\s+-r\s+.*-f', # rm -r ... -f
r'\brm\s+-f\s+.*-r', # rm -f ... -r
]
# Check for dangerous patterns
for pattern in patterns:
if re.search(pattern, normalized):
return True
# Pattern 2: Check for rm with recursive flag targeting dangerous paths
dangerous_paths = [
r'/', # Root directory
r'/\*', # Root with wildcard
r'~', # Home directory
r'~/', # Home directory path
r'\$HOME', # Home environment variable
r'\.\.', # Parent directory references
r'\*', # Wildcards in general rm -rf context
r'\.', # Current directory
r'\.\s*$', # Current directory at end of command
]
if re.search(r'\brm\s+.*-[a-z]*r', normalized): # If rm has recursive flag
for path in dangerous_paths:
if re.search(path, normalized):
return True
return False
def is_env_file_access(tool_name, tool_input):
"""
Check if any tool is trying to access .env files containing sensitive data.
"""
if tool_name in ['Read', 'Edit', 'MultiEdit', 'Write', 'Bash']:
# Check file paths for file-based tools
if tool_name in ['Read', 'Edit', 'MultiEdit', 'Write']:
file_path = tool_input.get('file_path', '')
if '.env' in file_path and not file_path.endswith('.env.sample'):
return True
# Check bash commands for .env file access
elif tool_name == 'Bash':
command = tool_input.get('command', '')
# Pattern to detect .env file access (but allow .env.sample)
env_patterns = [
r'\b\.env\b(?!\.sample)', # .env but not .env.sample
r'cat\s+.*\.env\b(?!\.sample)', # cat .env
r'echo\s+.*>\s*\.env\b(?!\.sample)', # echo > .env
r'touch\s+.*\.env\b(?!\.sample)', # touch .env
r'cp\s+.*\.env\b(?!\.sample)', # cp .env
r'mv\s+.*\.env\b(?!\.sample)', # mv .env
]
for pattern in env_patterns:
if re.search(pattern, command):
return True
return False
def main():
try:
# Read JSON input from stdin
input_data = json.load(sys.stdin)
tool_name = input_data.get('tool_name', '')
tool_input = input_data.get('tool_input', {})
# Check for .env file access (blocks access to sensitive environment files)
if is_env_file_access(tool_name, tool_input):
print("BLOCKED: Access to .env files containing sensitive data is prohibited", file=sys.stderr)
print("Use .env.sample for template files instead", file=sys.stderr)
sys.exit(2) # Exit code 2 blocks tool call and shows error to Claude
# Check for dangerous rm -rf commands
if tool_name == 'Bash':
command = tool_input.get('command', '')
# Block rm -rf commands with comprehensive pattern matching
if is_dangerous_rm_command(command):
print("BLOCKED: Dangerous rm command detected and prevented", file=sys.stderr)
sys.exit(2) # Exit code 2 blocks tool call and shows error to Claude
# Append new data
append_log_data('pre_tool_use.json', input_data)
sys.exit(0)
except json.JSONDecodeError:
# Gracefully handle JSON decode errors
sys.exit(0)
except Exception:
# Handle any other errors gracefully
sys.exit(0)
if __name__ == '__main__':
main()