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 #!/bin/bash
# Auto-format code before writing # Auto-format code before writing
# Hook: PreToolUse:Write # 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 # Extract file_path using sed (compatible with all platforms)
echo "Usage: $0 <file_path>" FILE_PATH=$(echo "$INPUT" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
exit 1
if [ -z "$FILE_PATH" ] || [ ! -f "$FILE_PATH" ]; then
exit 0
fi fi
# Detect file type and format accordingly # Detect file type and format accordingly
case "$FILE" in case "$FILE_PATH" in
*.js|*.jsx|*.ts|*.tsx) *.js|*.jsx|*.ts|*.tsx)
if command -v prettier &> /dev/null; then if command -v prettier &> /dev/null; then
echo "Formatting JavaScript/TypeScript file: $FILE" prettier --write "$FILE_PATH" 2>/dev/null
prettier --write "$FILE"
fi fi
;; ;;
*.py) *.py)
if command -v black &> /dev/null; then if command -v black &> /dev/null; then
echo "Formatting Python file: $FILE" black "$FILE_PATH" 2>/dev/null
black "$FILE"
fi fi
;; ;;
*.go) *.go)
if command -v gofmt &> /dev/null; then if command -v gofmt &> /dev/null; then
echo "Formatting Go file: $FILE" gofmt -w "$FILE_PATH" 2>/dev/null
gofmt -w "$FILE"
fi fi
;; ;;
*.rs) *.rs)
if command -v rustfmt &> /dev/null; then if command -v rustfmt &> /dev/null; then
echo "Formatting Rust file: $FILE" rustfmt "$FILE_PATH" 2>/dev/null
rustfmt "$FILE"
fi fi
;; ;;
*.java) *.java)
if command -v google-java-format &> /dev/null; then if command -v google-java-format &> /dev/null; then
echo "Formatting Java file: $FILE" google-java-format -i "$FILE_PATH" 2>/dev/null
google-java-format -i "$FILE"
fi fi
;; ;;
esac esac
+14 -4
View File
@@ -1,8 +1,21 @@
#!/bin/bash #!/bin/bash
# Log all bash commands # Log all bash commands
# Hook: PostToolUse:Bash # 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") TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
LOGFILE="$HOME/.claude/bash-commands.log" LOGFILE="$HOME/.claude/bash-commands.log"
@@ -12,7 +25,4 @@ mkdir -p "$(dirname "$LOGFILE")"
# Log the command # Log the command
echo "[$TIMESTAMP] $COMMAND" >> "$LOGFILE" echo "[$TIMESTAMP] $COMMAND" >> "$LOGFILE"
# Optional: Log to system log as well
# logger -t "claude-bash" "$COMMAND"
exit 0 exit 0
+34 -28
View File
@@ -1,61 +1,67 @@
#!/bin/bash #!/bin/bash
# Security scan on file write # Security scan on file write
# Hook: PostToolUse: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 # Extract file_path using sed (compatible with all platforms including Windows Git Bash)
echo "Usage: $0 <file_path>" # 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 exit 0
fi 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 # Check for hardcoded passwords
if grep -qE "(password|passwd|pwd)\s*=\s*['\"][^'\"]+['\"]" "$FILE"; then # Handles both JSON format ("password": "value") and code format (password = 'value')
echo "⚠️ WARNING: Potential hardcoded password detected in $FILE" if grep -qiE '"password"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
ISSUES_FOUND=1 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 fi
# Check for hardcoded API keys # Check for hardcoded API keys
if grep -qE "(api[_-]?key|apikey|access[_-]?token)\s*=\s*['\"][^'\"]+['\"]" "$FILE"; then if grep -qiE '"(api[_-]?key|apikey|access[_-]?token)"[[:space:]]*:[[:space:]]*"[^"]+"' "$FILE_PATH" 2>/dev/null; then
echo "⚠️ WARNING: Potential hardcoded API key detected in $FILE" ISSUES="${ISSUES}- WARNING: Potential hardcoded API key detected\n"
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
fi fi
# Check for private keys # Check for private keys
if grep -q "BEGIN.*PRIVATE KEY" "$FILE"; then if grep -q "BEGIN.*PRIVATE KEY" "$FILE_PATH" 2>/dev/null; then
echo "⚠️ WARNING: Private key detected in $FILE" ISSUES="${ISSUES}- WARNING: Private key detected\n"
ISSUES_FOUND=1
fi fi
# Check for AWS keys # Check for AWS keys
if grep -qE "AKIA[0-9A-Z]{16}" "$FILE"; then if grep -qE "AKIA[0-9A-Z]{16}" "$FILE_PATH" 2>/dev/null; then
echo "⚠️ WARNING: AWS access key detected in $FILE" ISSUES="${ISSUES}- WARNING: AWS access key detected\n"
ISSUES_FOUND=1
fi fi
# Scan with semgrep if available # Scan with semgrep if available
if command -v semgrep &> /dev/null; then 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 fi
# Scan with trufflehog if available # Scan with trufflehog if available
if command -v trufflehog &> /dev/null; then 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 fi
if [ $ISSUES_FOUND -eq 0 ]; then # If issues found, output as additionalContext (non-blocking warning)
echo "✅ No security issues found" if [ -n "$ISSUES" ]; then
printf '{"additionalContext": "Security scan found issues in %s:\n%sPlease review and use environment variables instead."}' "$FILE_PATH" "$ISSUES"
fi fi
# Don't block the operation, just warn
exit 0 exit 0
+19 -11
View File
@@ -1,11 +1,21 @@
#!/bin/bash #!/bin/bash
# Validate user prompts # Validate user prompts
# Hook: UserPromptSubmit # Hook: UserPromptSubmit
#
# Reads the user prompt from stdin JSON and blocks dangerous operations.
#
# Compatible with: macOS, Linux, Windows (Git Bash)
# Read prompt from stdin # Read JSON input from stdin (Claude Code hook protocol)
PROMPT=$(cat) 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 # Check for dangerous operations
DANGEROUS_PATTERNS=( DANGEROUS_PATTERNS=(
@@ -18,26 +28,24 @@ DANGEROUS_PATTERNS=(
for pattern in "${DANGEROUS_PATTERNS[@]}"; do for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$PROMPT" | grep -qi "$pattern"; then if echo "$PROMPT" | grep -qi "$pattern"; then
echo "❌ Blocked: Dangerous operation detected: $pattern" printf '{"decision": "block", "reason": "Dangerous operation detected: %s"}' "$pattern"
exit 1 exit 0
fi fi
done done
# Check for production deployments # Check for production deployments
if echo "$PROMPT" | grep -qiE "(deploy|push).*production"; then if echo "$PROMPT" | grep -qiE "(deploy|push).*production"; then
if [ ! -f ".deployment-approved" ]; then if [ ! -f ".deployment-approved" ]; then
echo "❌ Blocked: Production deployment requires approval" echo '{"decision": "block", "reason": "Production deployment requires approval. Create .deployment-approved file to proceed."}'
echo "Create .deployment-approved file to proceed" exit 0
exit 1
fi fi
fi fi
# Check for required context in certain operations # Check for required context in certain operations
if echo "$PROMPT" | grep -qi "refactor"; then if echo "$PROMPT" | grep -qi "refactor"; then
if [ ! -f "tests/" ] && [ ! -f "test/" ]; then if [ ! -d "tests" ] && [ ! -d "test" ]; then
echo "⚠️ Warning: Refactoring without tests may be risky" printf '{"additionalContext": "Warning: Refactoring without tests may be risky. Consider writing tests first."}'
fi fi
fi fi
echo "✅ Prompt validation passed"
exit 0 exit 0