diff --git a/06-hooks/format-code.sh b/06-hooks/format-code.sh index 090d8d6..e59a132 100644 --- a/06-hooks/format-code.sh +++ b/06-hooks/format-code.sh @@ -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 " - 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 diff --git a/06-hooks/log-bash.sh b/06-hooks/log-bash.sh index 59b9efe..a6871f6 100644 --- a/06-hooks/log-bash.sh +++ b/06-hooks/log-bash.sh @@ -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 diff --git a/06-hooks/security-scan.sh b/06-hooks/security-scan.sh index 58ff56c..d2b7e45 100644 --- a/06-hooks/security-scan.sh +++ b/06-hooks/security-scan.sh @@ -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 " +# 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 diff --git a/06-hooks/validate-prompt.sh b/06-hooks/validate-prompt.sh index 312bcd1..882f74e 100644 --- a/06-hooks/validate-prompt.sh +++ b/06-hooks/validate-prompt.sh @@ -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