surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| 1 | #!/bin/bash |
| 2 | # Example hook that reads plugin settings from .claude/my-plugin.local.md |
| 3 | # Demonstrates the complete pattern for settings-driven hook behavior |
| 4 | |
| 5 | set -euo pipefail |
| 6 | |
| 7 | # Define settings file path |
| 8 | SETTINGS_FILE=".claude/my-plugin.local.md" |
| 9 | |
| 10 | # Quick exit if settings file doesn't exist |
| 11 | if [[ ! -f "$SETTINGS_FILE" ]]; then |
| 12 | # Plugin not configured - use defaults or skip |
| 13 | exit 0 |
| 14 | fi |
| 15 | |
| 16 | # Parse YAML frontmatter (everything between --- markers) |
| 17 | FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$SETTINGS_FILE") |
| 18 | |
| 19 | # Extract configuration fields |
| 20 | ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/') |
| 21 | STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/') |
| 22 | MAX_SIZE=$(echo "$FRONTMATTER" | grep '^max_file_size:' | sed 's/max_file_size: *//') |
| 23 | |
| 24 | # Quick exit if disabled |
| 25 | if [[ "$ENABLED" != "true" ]]; then |
| 26 | exit 0 |
| 27 | fi |
| 28 | |
| 29 | # Read hook input |
| 30 | input=$(cat) |
| 31 | file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty') |
| 32 | |
| 33 | # Apply configured validation |
| 34 | if [[ "$STRICT_MODE" == "true" ]]; then |
| 35 | # Strict mode: apply all checks |
| 36 | if [[ "$file_path" == *".."* ]]; then |
| 37 | echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Path traversal blocked (strict mode)"}' >&2 |
| 38 | exit 2 |
| 39 | fi |
| 40 | |
| 41 | if [[ "$file_path" == *".env"* ]] || [[ "$file_path" == *"secret"* ]]; then |
| 42 | echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "Sensitive file blocked (strict mode)"}' >&2 |
| 43 | exit 2 |
| 44 | fi |
| 45 | else |
| 46 | # Standard mode: basic checks only |
| 47 | if [[ "$file_path" == "/etc/"* ]] || [[ "$file_path" == "/sys/"* ]]; then |
| 48 | echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "System path blocked"}' >&2 |
| 49 | exit 2 |
| 50 | fi |
| 51 | fi |
| 52 | |
| 53 | # Check file size if configured |
| 54 | if [[ -n "$MAX_SIZE" ]] && [[ "$MAX_SIZE" =~ ^[0-9]+$ ]]; then |
| 55 | content=$(echo "$input" | jq -r '.tool_input.content // empty') |
| 56 | content_size=${#content} |
| 57 | |
| 58 | if [[ $content_size -gt $MAX_SIZE ]]; then |
| 59 | echo '{"hookSpecificOutput": {"permissionDecision": "deny"}, "systemMessage": "File exceeds configured max size: '"$MAX_SIZE"' bytes"}' >&2 |
| 60 | exit 2 |
| 61 | fi |
| 62 | fi |
| 63 | |
| 64 | # All checks passed |
| 65 | exit 0 |