From 4b03334304b5f594e67abf07aac141ef80352c3d Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Tue, 12 May 2026 17:40:37 -0700 Subject: [PATCH] feat: Claude Code pre-bash hooks for PowerShell and path enforcement Block inline pwsh -Command/-c (force .ps1 file approach) and Windows backslash paths in Bash commands (enforce forward slashes). Eliminates the 2-3 retry loop on PowerShell operations and prevents the /tmp path mismatch that caused the stale-payload Syncro incident. Co-Authored-By: Claude Sonnet 4.6 --- .claude/hooks/pre-bash-backslash.sh | 26 ++++++++++++++++++++++++++ .claude/hooks/pre-bash-pwsh-script.sh | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 .claude/hooks/pre-bash-backslash.sh create mode 100644 .claude/hooks/pre-bash-pwsh-script.sh diff --git a/.claude/hooks/pre-bash-backslash.sh b/.claude/hooks/pre-bash-backslash.sh new file mode 100644 index 0000000..ce00f26 --- /dev/null +++ b/.claude/hooks/pre-bash-backslash.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Pre-tool hook: block Windows backslash paths in Bash commands. +# +# Blocks patterns like C:\Users\foo passed inside Bash command strings. +# Enforces forward slashes: C:/Users/foo +# +# Why: Git Bash mangles backslash paths — C:\tmp writes to a different +# directory than the Write tool's C:\tmp, causing stale payload bugs. + +input=$(cat) +cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) + +# Match a drive letter followed by a literal backslash in the command. +# In the extracted command string (not JSON-escaped), backslash is just \. +if echo "$cmd" | grep -qE '[A-Za-z]:\\[A-Za-z/\\]'; then + echo "BLOCKED: Use forward slashes for Windows paths in Bash commands." + echo "" + echo " Wrong: C:\\Users\\guru\\file.txt" + echo " Correct: C:/Users/guru/file.txt" + echo "" + echo "Git Bash converts backslash paths unpredictably. PowerShell and Windows" + echo "APIs both accept forward slashes without issue." + exit 2 +fi + +exit 0 diff --git a/.claude/hooks/pre-bash-pwsh-script.sh b/.claude/hooks/pre-bash-pwsh-script.sh new file mode 100644 index 0000000..ed640d2 --- /dev/null +++ b/.claude/hooks/pre-bash-pwsh-script.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Pre-tool hook: block inline PowerShell, enforce .ps1 file approach. +# +# Blocks powershell.exe -Command and pwsh -Command / pwsh -c inline execution. +# Forces: write a .ps1 file, then run pwsh -NoProfile -File script.ps1 +# +# Why: Git Bash expands $_ and mangles quoting before PowerShell sees the +# command. Inline execution fails 2-3 times before landing on the .ps1 approach. + +input=$(cat) +cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) + +# Match: (powershell[.exe] | pwsh) followed by -Command or -c (as a flag, not a filename) +if echo "$cmd" | grep -qiE '^\s*(powershell(\.exe)?|pwsh)\s+(-Command|-c) ' || \ + echo "$cmd" | grep -qiE '^\s*(powershell(\.exe)?|pwsh)\s+(-Command|-c)$'; then + echo "BLOCKED: Do not use powershell.exe or pwsh with inline -Command/-c arguments." + echo "" + echo "Git Bash mangles quoting and variable expansion before PowerShell sees the command." + echo "" + echo "Correct approach:" + echo " 1. Write the script using the Write tool to a .ps1 file" + echo " 2. Run: pwsh -NoProfile -File \"path/to/script.ps1\"" + exit 2 +fi + +exit 0