mirror of
https://github.com/luongnv89/claude-howto.git
synced 2026-06-01 10:31:33 +02:00
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:
+18
-15
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user