← Files Unix CopilotARCHIVED FILE

scripts/command_review.py

4.37 KB · Nov 7, 2026 · 12:49 UTC

↓ Download file

See the change to this file →

#!/usr/bin/env python3
"""Offline *heuristic* review of shell command text. Never executes commands.

Limitations: regex patterns are not a shell parser. Comments, quotes, aliases,
command substitution, eval and shell environment can affect true behavior. A clean
result does not establish safety. Use this only for triage and demonstrations.
"""
import argparse
import json
import re
from typing import Dict, List

PATTERNS = [
  ("recursive-delete", "high", r"\b(?:rm|/bin/rm)\s+-(?:[a-zA-Z]*[rf][a-zA-Z]*[rf]?|[rRfF]+)\b", "Recursive deletion requested; enumerate the exact scope before any write."),
  ("recursive-delete-long", "high", r"\brm\s+--(?:recursive|force)(?:\s+--(?:recursive|force))?\b", "Review the exact recursive delete target."),
  ("disk-write", "high", r"\bdd\b[^\n]*\bof\s*=\s*/dev/", "Raw device overwrite requires verified target and explicit authorization."),
  ("format-device", "high", r"\b(?:mkfs(?:\.[a-z0-9]+)?|wipefs|fdisk|parted)\b", "Disk/filesystem operations may destroy data; preview target first."),
  ("fetch-execute", "high", r"\b(?:curl|wget)\b[^\n|]*\|\s*(?:sudo\s+)?(?:sh|bash|zsh)\b", "Review provenance and fetched contents; do not pipe arbitrary downloads to a shell."),
  ("broad-permissions", "high", r"\bchmod\s+-(?:[a-z]*R[a-z]*|[a-z]*r[a-z]*)\s+777\b", "World-writable recursive permissions are unsafe as a generic fix."),
  ("recursive-owner-change", "high", r"\bchown\s+-(?:[a-z]*R[a-z]*)\b", "Recursive ownership change requires exact scope and rollback review."),
  ("privileged-command", "high", r"\bsudo\s+", "Elevated privilege must be limited to an authorized operation."),
  ("shell-eval", "medium", r"(?:^|[;&|\n]\s*)eval\s+", "Dynamic shell evaluation can execute unintended code; prefer direct argument passing."),
  ("force-kill", "high", r"\bkill\s+-(?:9|KILL)\b|\bkillall\s+", "Confirm process identity and impact before termination."),
  ("rsync-delete", "high", r"\brsync\b[^\n]*--delete\b", "Rsync delete flag may remove destination files; run a verified dry-run first."),
  ("csv-cut", "medium", r"\bcut\s+-d\s*['\"]?,['\"]?", "Plain cut does not parse arbitrary CSV with quoting or multiline fields."),
  ("csv-awk", "medium", r"\bawk\s+-(?:F|v\s+FS=)['\"]?,['\"]?", "Plain comma-separated awk does not parse general CSV quoting."),
  ("inline-secret", "high", r"(?i)\b(?:TOKEN|API_KEY|PASSWORD|SECRET)\s*=\s*['\"]?(?:sk-[A-Za-z0-9_-]{12,}|[A-Za-z0-9/+]{30,})", "Avoid command-line/history exposure of secret-like values."),
  ("unverified-variable-delete", "high", r"\brm\s+(?:-[A-Za-z]+\s+)*['\"]?\$\{?(?:TARGET|PATH|DIR|ROOT|BASE)\}?", "Variable deletion target may be empty, broad or unexpected; resolve and preview before action."),
]
COMPILED=[(i,severity,re.compile(regex,re.IGNORECASE if i in {"fetch-execute", "format-device"} else 0),message) for i,severity,regex,message in PATTERNS]


def analyze(command: str) -> Dict:
    if not isinstance(command,str):
        raise TypeError("command must be a string")
    findings: List[Dict]=[]
    for rule_id,severity,pattern,message in COMPILED:
        for match in pattern.finditer(command):
            line=command.count("\n",0,match.start())+1
            findings.append({"id":rule_id,"severity":severity,"line":line,"message":message})
    findings.sort(key=lambda f:(f["line"],f["id"]))
    risk="high" if any(f["severity"]=="high" for f in findings) else ("medium" if findings else "unknown")
    return {"risk":risk,"findings":findings,"executed":False,"warning":"Heuristic text-only review, not full parsing or security certification; no commands were executed."}


def main():
    parser=argparse.ArgumentParser(description="Review shell command TEXT without running it.")
    parser.add_argument("command",nargs="?",help="Shell command string; if omitted, read stdin.")
    parser.add_argument("--json",action="store_true",help="Output JSON (default text).")
    args=parser.parse_args()
    if args.command is None:
        import sys
        command=sys.stdin.read()
    else:
        command=args.command
    result=analyze(command)
    if args.json:
        print(json.dumps(result,indent=2))
    else:
        print("Risk: "+result["risk"])
        for f in result["findings"]:
            print(f"Line {f['line']}: {f['severity']} [{f['id']}] — {f['message']}")
        print(result["warning"])
    return 0  # A heuristic result is NOT permission to execute the command.

if __name__=="__main__":
    raise SystemExit(main())

SHA-256: 1cdc0fe984adf96bb277548b9606eed30ba4f0929567251887024cd8981efc06