{
"event": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "\/var\/www\/scripts\/hooks\/block_password_exposure.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nClaude Code Hook: Blockiert Passwort-Exposition in Bash-Commands\n\nHook Type: PreToolUse\nBlockiert: Bash-Befehle die Passwörter im Klartext enthalten\nEmpfehlung: Passwörter aus \/etc\/environment oder .env laden\n\nPatterns die blockiert werden:\n- DB_PASSWORD=\"wert\"\n- MARIADB_ROOT_PASSWORD=\"wert\"\n- PASSWORD=\"wert\"\n- --password=wert\n- -p\"wert\" oder -p'wert'\n\"\"\"\nimport json\nimport re\nimport sys\n\n\n# Patterns die Passwort-Exposition erkennen\nBLOCKED_PATTERNS = [\n # Environment variable assignments with actual values\n r'DB_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # DB_PASSWORD=\"value\"\n r'DB_PASSWORD=[^\\s\"\\']+\\s', # DB_PASSWORD=value (unquoted)\n r'MARIADB_ROOT_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MARIADB_ROOT_PASSWORD=\"value\"\n r'MARIADB_ROOT_PASSWORD=[^\\s\"\\']+\\s', # MARIADB_ROOT_PASSWORD=value\n r'MYSQL_ROOT_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MYSQL_ROOT_PASSWORD=\"value\"\n r'MYSQL_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MYSQL_PASSWORD=\"value\"\n r'API_KEY=[\"\\'][^\"\\']+[\"\\']', # API_KEY=\"value\"\n r'ANTHROPIC_API_KEY=[\"\\'][^\"\\']+[\"\\']', # ANTHROPIC_API_KEY=\"value\"\n r'SECRET=[\"\\'][^\"\\']+[\"\\']', # SECRET=\"value\"\n r'TOKEN=[\"\\'][^\"\\']+[\"\\']', # TOKEN=\"value\"\n # Generic PASSWORD with value (but not empty)\n r'PASSWORD=[\"\\'][^\"\\']+[\"\\']', # PASSWORD=\"value\"\n]\n\n# Patterns die erlaubt sind (false positives vermeiden)\nALLOWED_PATTERNS = [\n r'\\$DB_PASSWORD', # Using variable $DB_PASSWORD\n r'\\$\\{DB_PASSWORD\\}', # Using ${DB_PASSWORD}\n r'DB_PASSWORD=\\$', # DB_PASSWORD=$OTHER_VAR\n r'DB_PASSWORD=\"\"', # Empty assignment\n r\"DB_PASSWORD=''\", # Empty assignment\n r'getenv\\([\"\\']DB_PASSWORD', # PHP getenv\n r'os\\.environ\\.get\\([\"\\']DB_PASSWORD', # Python os.environ\n r'get_db_password\\(\\)', # Calling getter function\n]\n\nERROR_MESSAGE = \"\"\"\nBLOCKIERT: Passwort-Exposition im Bash-Command erkannt!\n\nDu hast versucht, ein Passwort im Klartext in einem Bash-Befehl zu übergeben.\nDas exponiert das Passwort in:\n - ps aux (für alle User sichtbar)\n - \/proc\/*\/cmdline\n - bash_history\n - Konversations-Logs\n\nRICHTIG: Passwort aus Environment laden\n Das Passwort ist bereits in \/etc\/environment gesetzt.\n Einfach das Script ohne Passwort-Übergabe starten:\n\n # RICHTIG:\n .\/venv\/bin\/python script.py\n\n # FALSCH:\n DB_PASSWORD=\"xxx\" .\/venv\/bin\/python script.py\n\n Das Script lädt das Passwort automatisch via:\n from config import get_db_password\n password = get_db_password()\n\nDokumentation: \/docs\/betrieb\/secrets\n\"\"\".strip()\n\n\ndef check_command(command: str) -> tuple[bool, str]:\n \"\"\"\n Prüft ob Befehl Passwort-Exposition enthält.\n\n Returns:\n (allowed, error_message)\n \"\"\"\n # Erst prüfen ob es ein erlaubtes Pattern ist\n for pattern in ALLOWED_PATTERNS:\n if re.search(pattern, command, re.IGNORECASE):\n return True, \"\"\n\n # Dann prüfen ob blockiertes Pattern vorhanden\n for pattern in BLOCKED_PATTERNS:\n if re.search(pattern, command, re.IGNORECASE):\n return False, ERROR_MESSAGE\n\n return True, \"\"\n\n\ndef main():\n \"\"\"\n Hook Entry Point (PreToolUse).\n\n Input (stdin): JSON mit tool_name und tool_input\n Output (stdout): JSON mit allow\/deny\n \"\"\"\n try:\n # Lese Hook-Input von stdin\n hook_input = json.loads(sys.stdin.read())\n\n tool_name = hook_input.get(\"tool_name\", \"\")\n tool_input = hook_input.get(\"tool_input\", {})\n\n # Nur Bash-Tool prüfen\n if tool_name != \"Bash\":\n sys.exit(0)\n\n # Befehl extrahieren\n command = tool_input.get(\"command\", \"\")\n\n # Prüfen ob Befehl blockiert werden soll\n allowed, error_msg = check_command(command)\n\n if not allowed:\n # Output im Claude Code Format\n output = {\n \"hookSpecificOutput\": {\n \"hookEventName\": \"PreToolUse\",\n \"permissionDecision\": \"deny\",\n \"permissionDecisionReason\": error_msg\n }\n }\n print(json.dumps(output))\n sys.exit(1)\n\n # Allowed: kein Output nötig\n sys.exit(0)\n\n except Exception as e:\n # Bei Fehler: Fail-Open (durchlassen, aber warnen)\n print(f\"Hook error (fail-open): {e}\", file=sys.stderr)\n sys.exit(0)\n\n\nif __name__ == \"__main__\":\n main()\n"
}
}
{
"tool_response": {
"type": "create",
"filePath": "\/var\/www\/scripts\/hooks\/block_password_exposure.py",
"content": "#!\/usr\/bin\/env python3\n\"\"\"\nClaude Code Hook: Blockiert Passwort-Exposition in Bash-Commands\n\nHook Type: PreToolUse\nBlockiert: Bash-Befehle die Passwörter im Klartext enthalten\nEmpfehlung: Passwörter aus \/etc\/environment oder .env laden\n\nPatterns die blockiert werden:\n- DB_PASSWORD=\"wert\"\n- MARIADB_ROOT_PASSWORD=\"wert\"\n- PASSWORD=\"wert\"\n- --password=wert\n- -p\"wert\" oder -p'wert'\n\"\"\"\nimport json\nimport re\nimport sys\n\n\n# Patterns die Passwort-Exposition erkennen\nBLOCKED_PATTERNS = [\n # Environment variable assignments with actual values\n r'DB_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # DB_PASSWORD=\"value\"\n r'DB_PASSWORD=[^\\s\"\\']+\\s', # DB_PASSWORD=value (unquoted)\n r'MARIADB_ROOT_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MARIADB_ROOT_PASSWORD=\"value\"\n r'MARIADB_ROOT_PASSWORD=[^\\s\"\\']+\\s', # MARIADB_ROOT_PASSWORD=value\n r'MYSQL_ROOT_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MYSQL_ROOT_PASSWORD=\"value\"\n r'MYSQL_PASSWORD=[\"\\'][^\"\\']+[\"\\']', # MYSQL_PASSWORD=\"value\"\n r'API_KEY=[\"\\'][^\"\\']+[\"\\']', # API_KEY=\"value\"\n r'ANTHROPIC_API_KEY=[\"\\'][^\"\\']+[\"\\']', # ANTHROPIC_API_KEY=\"value\"\n r'SECRET=[\"\\'][^\"\\']+[\"\\']', # SECRET=\"value\"\n r'TOKEN=[\"\\'][^\"\\']+[\"\\']', # TOKEN=\"value\"\n # Generic PASSWORD with value (but not empty)\n r'PASSWORD=[\"\\'][^\"\\']+[\"\\']', # PASSWORD=\"value\"\n]\n\n# Patterns die erlaubt sind (false positives vermeiden)\nALLOWED_PATTERNS = [\n r'\\$DB_PASSWORD', # Using variable $DB_PASSWORD\n r'\\$\\{DB_PASSWORD\\}', # Using ${DB_PASSWORD}\n r'DB_PASSWORD=\\$', # DB_PASSWORD=$OTHER_VAR\n r'DB_PASSWORD=\"\"', # Empty assignment\n r\"DB_PASSWORD=''\", # Empty assignment\n r'getenv\\([\"\\']DB_PASSWORD', # PHP getenv\n r'os\\.environ\\.get\\([\"\\']DB_PASSWORD', # Python os.environ\n r'get_db_password\\(\\)', # Calling getter function\n]\n\nERROR_MESSAGE = \"\"\"\nBLOCKIERT: Passwort-Exposition im Bash-Command erkannt!\n\nDu hast versucht, ein Passwort im Klartext in einem Bash-Befehl zu übergeben.\nDas exponiert das Passwort in:\n - ps aux (für alle User sichtbar)\n - \/proc\/*\/cmdline\n - bash_history\n - Konversations-Logs\n\nRICHTIG: Passwort aus Environment laden\n Das Passwort ist bereits in \/etc\/environment gesetzt.\n Einfach das Script ohne Passwort-Übergabe starten:\n\n # RICHTIG:\n .\/venv\/bin\/python script.py\n\n # FALSCH:\n DB_PASSWORD=\"xxx\" .\/venv\/bin\/python script.py\n\n Das Script lädt das Passwort automatisch via:\n from config import get_db_password\n password = get_db_password()\n\nDokumentation: \/docs\/betrieb\/secrets\n\"\"\".strip()\n\n\ndef check_command(command: str) -> tuple[bool, str]:\n \"\"\"\n Prüft ob Befehl Passwort-Exposition enthält.\n\n Returns:\n (allowed, error_message)\n \"\"\"\n # Erst prüfen ob es ein erlaubtes Pattern ist\n for pattern in ALLOWED_PATTERNS:\n if re.search(pattern, command, re.IGNORECASE):\n return True, \"\"\n\n # Dann prüfen ob blockiertes Pattern vorhanden\n for pattern in BLOCKED_PATTERNS:\n if re.search(pattern, command, re.IGNORECASE):\n return False, ERROR_MESSAGE\n\n return True, \"\"\n\n\ndef main():\n \"\"\"\n Hook Entry Point (PreToolUse).\n\n Input (stdin): JSON mit tool_name und tool_input\n Output (stdout): JSON mit allow\/deny\n \"\"\"\n try:\n # Lese Hook-Input von stdin\n hook_input = json.loads(sys.stdin.read())\n\n tool_name = hook_input.get(\"tool_name\", \"\")\n tool_input = hook_input.get(\"tool_input\", {})\n\n # Nur Bash-Tool prüfen\n if tool_name != \"Bash\":\n sys.exit(0)\n\n # Befehl extrahieren\n command = tool_input.get(\"command\", \"\")\n\n # Prüfen ob Befehl blockiert werden soll\n allowed, error_msg = check_command(command)\n\n if not allowed:\n # Output im Claude Code Format\n output = {\n \"hookSpecificOutput\": {\n \"hookEventName\": \"PreToolUse\",\n \"permissionDecision\": \"deny\",\n \"permissionDecisionReason\": error_msg\n }\n }\n print(json.dumps(output))\n sys.exit(1)\n\n # Allowed: kein Output nötig\n sys.exit(0)\n\n except Exception as e:\n # Bei Fehler: Fail-Open (durchlassen, aber warnen)\n print(f\"Hook error (fail-open): {e}\", file=sys.stderr)\n sys.exit(0)\n\n\nif __name__ == \"__main__\":\n main()\n",
"structuredPatch": [],
"originalFile": null
}
}