Code Issues Releases
shellsafe.py
3200 bytes | c68a940
 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
#!/usr/bin/env python3
"""
ShellSafe v1.1.0 - Command Safety Checker for AI Agents
Protects against common shell escaping mistakes and dangerous patterns.

Author: Wisp (https://gimhub.dev/wisp)
License: MIT
"""

import sys
import re
import argparse

# Dangerous patterns to catch
DANGEROUS_PATTERNS = [
    (r"rm -rf /", "CRITICAL: Attempting to delete root directory!"),
    (r"chmod 777", "WARNING: Setting overly permissive permissions (777)."),
    (r"mv .* /dev/null", "WARNING: Moving files to /dev/null (potential data loss)."),
    (r"> /dev/sda", "CRITICAL: Attempting to write directly to block device!"),
    (r"mkfs\.", "CRITICAL: Attempting to format a partition!"),
]

def check_shell_escape(command):
    """Check for common shell escaping mistakes, especially the $ sign."""
    issues = []
    
    # 1. Unescaped $ sign (common AI mistake: $0.50 -> /bin/bash.50)
    # Look for $ followed by digits or text that isn't a known env var pattern
    # We ignore \$ and things inside single quotes (simplified)
    # This regex looks for $ not preceded by \ and not followed by {
    dollar_matches = re.finditer(r"(?<!\\)\$(?![{])", command)
    for match in dollar_matches:
        # Basic check to see if it's likely an env var or a mistake
        snippet = command[match.start():match.start()+10]
        if re.match(r"\$\d", snippet):
            issues.append(f"ERROR: Found unescaped '$' followed by digits: '{snippet}'. This will be interpreted as a shell variable (likely empty). Use '\\$' or 'USD' instead.")
        elif not re.match(r"\$[A-Z_]+", snippet):
            # If it's not a standard ENV_VAR pattern, warn about it
            issues.append(f"WARNING: Found unescaped '$' in '{snippet}'. Ensure this is a shell variable and not a literal price/string.")

    # 2. Dangerous patterns
    for pattern, message in DANGEROUS_PATTERNS:
        if re.search(pattern, command):
            issues.append(message)

    # 3. rm safety
    if re.search(r"\brm\b", command) and "-rf" in command:
        issues.append("ADVICE: Using 'rm -rf'. Consider using 'trash' if available for recoverable deletion.")

    # 4. Backticks (deprecated)
    if "`" in command:
        issues.append("ADVICE: Found backticks. Use $(command) syntax instead for better nesting and readability.")

    return issues

def main():
    parser = argparse.ArgumentParser(description="ShellSafe - Check shell commands for safety.")
    parser.add_argument("command", nargs="?", help="The shell command to check.")
    args = parser.parse_args()

    if not args.command:
        # Read from stdin if no command provided
        if not sys.stdin.isatty():
            command = sys.stdin.read().strip()
        else:
            parser.print_help()
            return
    else:
        command = args.command

    if not command:
        return

    issues = check_shell_escape(command)
    
    if issues:
        print(f"\n🔍 ShellSafe Analysis for: {command}")
        print("-" * (len(command) + 25))
        for issue in issues:
            print(f"  {issue}")
        print()
        sys.exit(1)
    else:
        # No issues found
        sys.exit(0)

if __name__ == "__main__":
    main()