Skip to content

Instantly share code, notes, and snippets.

@iamladi
Created January 3, 2026 10:21
Show Gist options
  • Select an option

  • Save iamladi/8e60aa2868f0e21eb21e4f81cebfceb7 to your computer and use it in GitHub Desktop.

Select an option

Save iamladi/8e60aa2868f0e21eb21e4f81cebfceb7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.8"
# ///
import json
import sys
import re
from pathlib import Path
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
# Ensure log directory exists
log_dir = Path.cwd() / 'logs'
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / 'pre_tool_use.json'
# Read existing log data or initialize empty list
if log_path.exists():
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []
# Append new data
log_data.append(input_data)
# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
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()
@iamladi
Copy link
Author

iamladi commented Jan 3, 2026

~/.claude/settings.json

"PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "uv run /Users/iamladi/.claude/hooks/pre_tool_use.py"
          }
        ]
      },

@iamladi
Copy link
Author

iamladi commented Jan 3, 2026

{
        "matcher": "WebSearch",
        "hooks": [
          {
            "type": "command",
            "command": "uv run python -c \"import json, sys, re; from datetime import datetime; input_data = json.load(sys.stdin); tool_input = input_data.get('tool_input', {}); query = tool_input.get('query', ''); current_year = str(datetime.now().year); has_year = re.search(r'\\\\b20\\\\d{2}\\\\b', query); has_temporal = any(word in query.lower() for word in ['latest', 'recent', 'current', 'new', 'now', 'today']); should_add_year = not has_year and not has_temporal; modified_query = f'{query} {current_year}' if should_add_year else query; output = {'hookSpecificOutput': {'hookEventName': 'PreToolUse', 'modifiedToolInput': {'query': modified_query}}}; print(json.dumps(output)); sys.exit(0)\"",
            "timeout": 5
          }
        ]
      }

@iamladi
Copy link
Author

iamladi commented Jan 3, 2026

"deny": [
      "Bash(rm -rf /)",
      "Bash(rm -rf /*)",
      "Bash(rm -rf ~)",
      "Bash(rm -rf $HOME)",
      "Bash(sudo rm -rf /)",
      "Bash(sudo rm -rf /*)",
      "Bash(fork bomb)",
      "Bash(dd if=/dev/zero of=/dev/sda)",
      "Bash(mkfs.ext4 /dev/sda)",
      "Bash(> /dev/sda)"
    ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment