#!/bin/bash # Claude Code PreToolUse hook for Bash (wired in via claude-settings.json): # commands that destroy things (containers, VMs, pools, disks, root-level # folders, cluster members…) get permissionDecision "ask", so Claude has to get # your OK even in auto mode (devdesk#27). It never blocks and never allows on # its own; anything else passes through untouched. # # DEVDESK_GUARD=0 in /etc/devdesk/env.local turns it off. Extra rules go in # /etc/devdesk/guard.local, one per line: an extended regex, a tab, a reason. # shellcheck source=libexec/devdesk-lib.sh . "${DEVDESK_LIBEXEC:-/usr/lib/devdesk}/devdesk-lib.sh" 2>/dev/null || exit 0 [ "${DEVDESK_GUARD:-1}" != 0 ] || exit 0 command=$(jq -rM '.tool_input.command // empty' 2>/dev/null) || exit 0 [ -n "$command" ] || exit 0 # Built-in rules: extended regex reason. rules=$( cat <<'RULES' \bpct[[:space:]]+destroy\b destroys a Proxmox container \bqm[[:space:]]+destroy\b destroys a Proxmox VM \bpvecm[[:space:]]+delnode\b removes a node from the Proxmox cluster \bpveceph[[:space:]]+(osd|pool|mon|mgr|mds|fs)[[:space:]]+destroy\b destroys Ceph components \bceph[[:space:]]+osd[[:space:]]+(purge|destroy|rm|out)\b removes or takes out a Ceph OSD \bceph[[:space:]]+osd[[:space:]]+pool[[:space:]]+(delete|rm)\b deletes a Ceph pool \bceph[[:space:]]+(fs[[:space:]]+rm|mon[[:space:]]+remove|auth[[:space:]]+del)\b removes Ceph components \brbd[[:space:]]+(rm|remove|purge)\b deletes RBD images \bzfs[[:space:]]+destroy\b destroys a ZFS dataset or snapshot \bzpool[[:space:]]+(destroy|labelclear)\b destroys a ZFS pool \b(lvremove|vgremove|pvremove|wipefs|shred)\b wipes volumes or disks \bmkfs(\.[a-z0-9]+)?\b formats a filesystem \bsgdisk[[:space:]]+(-[a-zA-Z]*Z|--zap) wipes a partition table \bdd\b[^|;&]*\bof=/dev/ writes raw to a device \brm[[:space:]]+(-[^[:space:]]*[[:space:]]+)*-[^[:space:]]*[rR][^[:space:]]*[[:space:]]+(-[^[:space:]]*[[:space:]]+)*['"]?/([a-z0-9_.*-]+/?)?['"]?([[:space:];&|)]|$) recursively deletes / or a top-level folder \bgit[[:space:]]+push\b[^|;&]*([[:space:]]-f\b|--force) force-pushes (rewrites remote history) \b(DROP[[:space:]]+(DATABASE|SCHEMA|TABLE)|TRUNCATE[[:space:]]+TABLE)\b drops database objects \bsystemctl[[:space:]]+(stop|restart|kill|disable|mask)[[:space:]]+devdesk-tmux ends every DevDesk session (^|[;&|][[:space:]]*|\bssh[[:space:]]+[^[:space:]]+[[:space:]]+['"]?|\bsystemctl[[:space:]]+)(reboot|poweroff|halt|shutdown)\b reboots or shuts down a machine \bkubectl[[:space:]]+delete\b deletes Kubernetes resources \bdocker[[:space:]]+(system[[:space:]]+prune|volume[[:space:]]+(rm|prune))\b deletes Docker data RULES cat "${DEVDESK_GUARD_LOCAL:-/etc/devdesk/guard.local}" 2>/dev/null ) while IFS=$'\t' read -r pattern reason; do if [ -z "$pattern" ] || [ "${pattern:0:1}" = "#" ]; then continue; fi if grep -Eiq -- "$pattern" <<<"$command" 2>/dev/null; then jq -cMn --arg r "DevDesk guard: this command ${reason:-matches a rule in guard.local}. Confirm before it runs." \ '{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: $r}}' exit 0 fi done <<<"$rules" exit 0