From 6d065cf3ee3324ea2d4bc05edd9a65c5e23c9502 Mon Sep 17 00:00:00 2001 From: Mike-Swanson Date: Sun, 24 May 2026 10:38:14 -0700 Subject: [PATCH] feat(hooks): block backslashed Windows-path redirects in bash Adds a PreToolUse(Bash) hook (block-backslash-winpath.sh) that rejects commands redirecting/writing to a backslashed Windows drive path (e.g. > D:\claudetools\ .claude\current-mode). On Git Bash those strip the backslashes and PUA-substitute ':' (U+F03A), creating garbled junk files that have repeatedly polluted the repo. The hook quote-strips the command first, so the pattern appearing inside strings or commit messages does not false-trigger; Windows-tool args (icacls, pwsh -File) and forward-slash/relative paths pass. Wired into settings.json so every machine picks it up on /sync. Pairs with the sync.sh staging guard. Also: CLAUDE.md note on the Windows mode-write path; record jq install on GURU-KALI. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/CLAUDE.md | 2 ++ .claude/hooks/block-backslash-winpath.sh | 37 ++++++++++++++++++++++++ .claude/machines/guru-kali.md | 3 +- .claude/settings.json | 12 ++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100755 .claude/hooks/block-backslash-winpath.sh diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 7c8a38c..92a2d80 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -67,6 +67,8 @@ echo dev > .claude/current-mode # substitute the actual mode name ``` This file is gitignored (machine-local). The `UserPromptSubmit` hook reads it to gate the lock check on dev mode. +**Windows/Git Bash:** always use the relative path above (or forward slashes — `/d/claudetools/.claude/current-mode`). NEVER a backslashed Windows path like `D:\claudetools\.claude\current-mode`: Git Bash strips the backslashes and substitutes the illegal `:` with a Unicode PUA char, creating a garbled junk file instead of writing the path. A `PreToolUse(Bash)` hook (`.claude/hooks/block-backslash-winpath.sh`) blocks such redirects; `sync.sh` also strips any that slip through before staging. + **Auto-initialization:** If `.claude/current-mode` is missing (e.g., fresh clone), the UserPromptSubmit hook automatically creates it with "general" as the default mode. No manual setup required. --- diff --git a/.claude/hooks/block-backslash-winpath.sh b/.claude/hooks/block-backslash-winpath.sh new file mode 100755 index 0000000..fc6bc01 --- /dev/null +++ b/.claude/hooks/block-backslash-winpath.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# PreToolUse(Bash) hook: block bash commands that REDIRECT/WRITE to a backslashed +# Windows drive path (e.g. `echo x > D:\claudetools\.claude\current-mode`). +# +# Why: under Git Bash / MSYS, a backslash is an escape char. `> D:\foo\bar` +# strips the backslashes and substitutes the illegal ':' with the Unicode +# Private-Use char U+F03A, creating a garbled junk file in the CWD instead of +# writing the intended path. These junk files then pollute the repo. Use a +# relative path or forward slashes (/d/claudetools/... or D:/claudetools/...). +# +# Only flags WRITE targets (> / >> / tee). Windows-tool arguments like +# `icacls "D:\Homes"` or `pwsh -File C:\x.ps1` are NOT redirects, so they pass. + +input=$(cat) +cmd=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) + +# Strip quoted substrings first, so the pattern appearing INSIDE a string or a +# commit message (e.g. git commit -m "... > D:\\path ...") does not false-trigger. +# A real bareword redirect target lives OUTSIDE quotes. (A redirect to a *quoted* +# path like `> "D:\x"` is rarer and is still caught by sync.sh's staging guard.) +bare=$(printf '%s' "$cmd" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g") + +# Block when, after quote-stripping, a redirect/tee writes to a bareword X:\ path. +if printf '%s' "$bare" | grep -qiP '(>>?|tee)\s*[A-Za-z]:\\'; then + echo "BLOCKED: do not redirect/write to a backslashed Windows path in bash." + echo "" + echo "Git Bash strips the backslashes and PUA-substitutes ':', creating a" + echo "garbled junk file instead of writing the path you intended." + echo "" + echo "Use one of these instead:" + echo " - relative path: echo dev > .claude/current-mode" + echo " - MSYS forward-slash: echo dev > /d/claudetools/.claude/current-mode" + echo " - drive forward-slash: echo dev > D:/claudetools/.claude/current-mode" + exit 2 +fi + +exit 0 diff --git a/.claude/machines/guru-kali.md b/.claude/machines/guru-kali.md index dc9165e..e6a7de5 100644 --- a/.claude/machines/guru-kali.md +++ b/.claude/machines/guru-kali.md @@ -33,7 +33,8 @@ | nmap | 7.99 (Kali security tooling) | | GuruRMM build dev libs | libgtk-3-dev, libayatana-appindicator3-dev, libxdo-dev, libssl-dev, pkg-config (for agent + tray builds) — added 2026-05-24 | | NVIDIA driver | nouveau (open-source) — NO proprietary driver / CUDA | -| jq / gh / docker / age / op / grepai / ollama | NOT installed | +| jq | 1.8.1 (added 2026-05-24, needed by hooks) | +| gh / docker / age / op / grepai / ollama | NOT installed | --- diff --git a/.claude/settings.json b/.claude/settings.json index 4ae5a53..2937e40 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -7,6 +7,18 @@ "verbose": false }, "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "bash -c 'h=\"${CLAUDE_PROJECT_DIR}/.claude/hooks/block-backslash-winpath.sh\"; [ -f \"$h\" ] && exec bash \"$h\" || exit 0'", + "timeout": 10 + } + ] + } + ], "UserPromptSubmit": [ { "hooks": [