Hooks, part two: prompt hooks and security
Prompt-based hooks use LLM reasoning instead of bash — supported on PreToolUse, Stop, SubagentStop, and UserPromptSubmit. Use them for context-aware judgment; keep command hooks for fast deterministic checks.
{ "PreToolUse": [{ "matcher": "Write|Edit", "hooks": [{ "type": "prompt", "prompt": "Validate file write safety. Check: system paths, credentials, path traversal, sensitive content. Return 'approve' or 'deny'." }] }], "Stop": [{ "matcher": "*", "hooks": [{ "type": "prompt", "prompt": "Verify task completion: tests run, build succeeded, questions answered. Return 'approve' to stop or 'block' with reason to continue." }] }]}Security rules for command hooks, from the plugin-dev toolkit:
- Validate all inputs — check tool names and paths before acting on them.
- Quote every variable —
echo "$file_path", neverecho $file_path(injection risk). - Deny path traversal and sensitive files — reject paths containing
..or.env. - Use
${CLAUDE_PLUGIN_ROOT}for portable script paths in plugins. - Set timeouts — defaults are 60s (command) and 30s (prompt).
#!/bin/bashset -euo pipefail
input=$(cat)file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# Deny path traversalif [[ "$file_path" == *".."* ]]; then echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2 exit 2fi
# Deny sensitive filesif [[ "$file_path" == *".env"* ]]; then echo '{"decision": "deny", "reason": "Sensitive file"}' >&2 exit 2fiCheck your understanding
Section titled “Check your understanding”Question 1Which events support prompt-based hooks?
Prompt hooks bring LLM judgment to those four events; others use command hooks.
Question 2Why quote bash variables in hook scripts?
echo $file_path with a malicious path can execute arbitrary words; quoting prevents it.
Question 3Two hooks match the same event. How do they run?
Matching hooks run in parallel and must not rely on each other's output.