fix(hooks): make hook scripts compatible with Windows Git Bash and use stdin JSON protocol (#49)

- Replace `grep -P` (Perl regex) with `sed` for JSON field extraction,
  as Windows Git Bash does not support `grep -P`
- Replace positional arg (`$1`) with stdin JSON parsing to match the
  actual Claude Code hook protocol (hooks receive data via stdin, not args)
- Fix JSON double-quote escaping in grep patterns that silently fails
  on Windows Git Bash
- Remove python3 dependency for JSON parsing, making scripts portable
- Add Windows Git Bash to compatibility notes in script headers

Affected scripts: security-scan.sh, validate-prompt.sh, log-bash.sh, format-code.sh

Co-authored-by: Bruce <binyuli1993@foxmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
binyu, li
2026-04-07 03:47:41 +08:00
committed by GitHub
parent a70777e9bc
commit 107153d5d7
4 changed files with 85 additions and 58 deletions
+18 -15
View File
@@ -1,44 +1,47 @@
#!/bin/bash
# Auto-format code before writing
# Hook: PreToolUse:Write
#
# Reads the target file path from stdin JSON and runs the appropriate formatter.
# Outputs updatedInput to pass the formatted content back.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
FILE=$1
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
if [ -z "$FILE" ]; then
echo "Usage: $0 <file_path>"
exit 1
# Extract file_path using sed (compatible with all platforms)
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi
# Detect file type and format accordingly
case "$FILE" in
case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx)
if command -v prettier &> /dev/null; then
echo "Formatting JavaScript/TypeScript file: $FILE"
prettier --write "$FILE"
prettier --write "$FILE_PATH" 2>/dev/null
fi
;;
*.py)
if command -v black &> /dev/null; then
echo "Formatting Python file: $FILE"
black "$FILE"
black "$FILE_PATH" 2>/dev/null
fi
;;
*.go)
if command -v gofmt &> /dev/null; then
echo "Formatting Go file: $FILE"
gofmt -w "$FILE"
gofmt -w "$FILE_PATH" 2>/dev/null
fi
;;
*.rs)
if command -v rustfmt &> /dev/null; then
echo "Formatting Rust file: $FILE"
rustfmt "$FILE"
rustfmt "$FILE_PATH" 2>/dev/null
fi
;;
*.java)
if command -v google-java-format &> /dev/null; then
echo "Formatting Java file: $FILE"
google-java-format -i "$FILE"
google-java-format -i "$FILE_PATH" 2>/dev/null
fi
;;
esac
+14 -4
View File
@@ -1,8 +1,21 @@
#!/bin/bash
# Log all bash commands
# Hook: PostToolUse:Bash
#
# Reads the executed command from stdin JSON and logs it to a file.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
# Extract the bash command from tool_input
COMMAND=$(echo "$INPUT" | sed -n 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$COMMAND" ]; then
exit 0
fi
COMMAND="$1"
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
LOGFILE="$HOME/.claude/bash-commands.log"
@@ -12,7 +25,4 @@ mkdir -p "$(dirname "$LOGFILE")"
# Log the command
echo "[$TIMESTAMP] $COMMAND" >> "$LOGFILE"
# Optional: Log to system log as well
# logger -t "claude-bash" "$COMMAND"
exit 0
+34 -28
View File
@@ -1,61 +1,67 @@
#!/bin/bash
# Security scan on file write
# Hook: PostToolUse:Write
#
# Scans files for hardcoded secrets, API keys, and credentials.
# Outputs a non-blocking warning via additionalContext when issues are found.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
FILE=$1
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
if [ -z "$FILE" ]; then
echo "Usage: $0 <file_path>"
# Extract file_path using sed (compatible with all platforms including Windows Git Bash)
# Avoids grep -P (not available on Windows Git Bash) and python3 dependency
FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi
echo "🔒 Running security scan on: $FILE"
# Skip binary files, vendor dirs, and build artifacts
case "$FILE_PATH" in
*.png|*.jpg|*.jpeg|*.gif|*.svg|*.ico|*.woff|*.woff2|*.ttf|*.eot) exit 0 ;;
*/node_modules/*|*/.git/*|*/dist/*|*/build/*) exit 0 ;;
esac
ISSUES_FOUND=0
ISSUES=""
# Check for hardcoded passwords
if grep -qE "(password|passwd|pwd)\s*=\s*['\"][^'\"]+['\"]" "$FILE"; then
echo "⚠️ WARNING: Potential hardcoded password detected in $FILE"
ISSUES_FOUND=1
# Handles both JSON format ("password": "value") and code format (password = 'value')
if grep -qiE '"password"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\n"
elif grep -qiE '(password|passwd|pwd)\s*=\s*'"'"'[^'"'"']+'"'"'' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded password detected\n"
fi
# Check for hardcoded API keys
if grep -qE "(api[_-]?key|apikey|access[_-]?token)\s*=\s*['\"][^'\"]+['\"]" "$FILE"; then
echo "⚠️ WARNING: Potential hardcoded API key detected in $FILE"
ISSUES_FOUND=1
fi
# Check for hardcoded secrets
if grep -qE "(secret|token)\s*=\s*['\"][^'\"]+['\"]" "$FILE"; then
echo "⚠️ WARNING: Potential hardcoded secret detected in $FILE"
ISSUES_FOUND=1
if grep -qiE '"(api[_-]?key|apikey|access[_-]?token)"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Potential hardcoded API key detected\n"
fi
# Check for private keys
if grep -q "BEGIN.*PRIVATE KEY" "$FILE"; then
echo "⚠️ WARNING: Private key detected in $FILE"
ISSUES_FOUND=1
if grep -q "BEGIN.*PRIVATE KEY" "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: Private key detected\n"
fi
# Check for AWS keys
if grep -qE "AKIA[0-9A-Z]{16}" "$FILE"; then
echo "⚠️ WARNING: AWS access key detected in $FILE"
ISSUES_FOUND=1
if grep -qE "AKIA[0-9A-Z]{16}" "$FILE_PATH" 2>/dev/null; then
ISSUES="${ISSUES}- WARNING: AWS access key detected\n"
fi
# Scan with semgrep if available
if command -v semgrep &> /dev/null; then
semgrep --config=auto "$FILE" --quiet 2>/dev/null
semgrep --config=auto "$FILE_PATH" --quiet 2>/dev/null
fi
# Scan with trufflehog if available
if command -v trufflehog &> /dev/null; then
trufflehog filesystem "$FILE" --only-verified --quiet 2>/dev/null
trufflehog filesystem "$FILE_PATH" --only-verified --quiet 2>/dev/null
fi
if [ $ISSUES_FOUND -eq 0 ]; then
echo "✅ No security issues found"
# If issues found, output as additionalContext (non-blocking warning)
if [ -n "$ISSUES" ]; then
printf '{"additionalContext": "Security scan found issues in %s:\n%sPlease review and use environment variables instead."}' "$FILE_PATH" "$ISSUES"
fi
# Don't block the operation, just warn
exit 0
+19 -11
View File
@@ -1,11 +1,21 @@
#!/bin/bash
# Validate user prompts
# Hook: UserPromptSubmit
#
# Reads the user prompt from stdin JSON and blocks dangerous operations.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
# Read prompt from stdin
PROMPT=$(cat)
# Read JSON input from stdin (Claude Code hook protocol)
INPUT=$(cat)
echo "🔍 Validating prompt..."
# Extract the prompt text from JSON input
# Claude Code sends: {"session_id": "...", "prompt": "user's message here"}
PROMPT=$(echo "$INPUT" | sed -n 's/.*"prompt"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$PROMPT" ]; then
exit 0
fi
# Check for dangerous operations
DANGEROUS_PATTERNS=(
@@ -18,26 +28,24 @@ DANGEROUS_PATTERNS=(
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$PROMPT" | grep -qi "$pattern"; then
echo "❌ Blocked: Dangerous operation detected: $pattern"
exit 1
printf '{"decision": "block", "reason": "Dangerous operation detected: %s"}' "$pattern"
exit 0
fi
done
# Check for production deployments
if echo "$PROMPT" | grep -qiE "(deploy|push).*production"; then
if [ ! -f ".deployment-approved" ]; then
echo "❌ Blocked: Production deployment requires approval"
echo "Create .deployment-approved file to proceed"
exit 1
echo '{"decision": "block", "reason": "Production deployment requires approval. Create .deployment-approved file to proceed."}'
exit 0
fi
fi
# Check for required context in certain operations
if echo "$PROMPT" | grep -qi "refactor"; then
if [ ! -f "tests/" ] && [ ! -f "test/" ]; then
echo "⚠️ Warning: Refactoring without tests may be risky"
if [ ! -d "tests" ] && [ ! -d "test" ]; then
printf '{"additionalContext": "Warning: Refactoring without tests may be risky. Consider writing tests first."}'
fi
fi
echo "✅ Prompt validation passed"
exit 0