← Files Unix CopilotARCHIVED FILE
scripts/command_review.py
4.37 KB · Nov 7, 2026 · 12:49 UTC
#!/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