diff --git a/hooks/no-vibes.sh b/hooks/no-vibes.sh index 26abf1d..b4310be 100755 --- a/hooks/no-vibes.sh +++ b/hooks/no-vibes.sh @@ -255,6 +255,17 @@ is_destructive_bash() { local candidate local pattern + # Precision guard: a pure-display command (echo/printf of literal args) cannot + # perform a destructive action even if it MENTIONS one (e.g. a reminder that + # echoes a force-push or rm incantation). Exempt it ONLY when the mentioned + # text has no way to execute or redirect: no pipe/background/chaining/redirect + # (| & ; < >), no backtick, and no $( command substitution. Anything that can + # execute or redirect falls through to full matching below — no bypass. + if printf '%s\n' "$command" | grep -Eq '^[[:space:]]*(echo|printf)([[:space:]]|$)' \ + && ! printf '%s\n' "$command" | grep -Eq '[|&;<>`]|\$\('; then + return 1 + fi + for candidate in "$command" "$(printf '%s\n' "$command" | sed "s/['\"\\\\]/ /g")"; do for pattern in "${_DESTRUCTIVE_PATTERNS[@]}"; do if printf '%s\n' "$candidate" | grep -Eiq -- "$pattern"; then diff --git a/tests/stress/no-vibes/negative/40-echo-mentions-force-push.json b/tests/stress/no-vibes/negative/40-echo-mentions-force-push.json new file mode 100644 index 0000000..398777e --- /dev/null +++ b/tests/stress/no-vibes/negative/40-echo-mentions-force-push.json @@ -0,0 +1 @@ +{"hook_event_name": "PreToolUse", "tool_name": "Bash", "tool_input": {"command": "echo 'reminder: avoid git push --force on shared branches'"}}