Skip to content

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.

hooks — prompt-based validation
{
"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 variableecho "$file_path", never echo $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).
validate-write.sh (excerpt)
#!/bin/bash
set -euo pipefail
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# Deny path traversal
if [[ "$file_path" == *".."* ]]; then
echo '{"decision": "deny", "reason": "Path traversal detected"}' >&2
exit 2
fi
# Deny sensitive files
if [[ "$file_path" == *".env"* ]]; then
echo '{"decision": "deny", "reason": "Sensitive file"}' >&2
exit 2
fi

Question 1Which events support prompt-based hooks?

Question 2Why quote bash variables in hook scripts?

Question 3Two hooks match the same event. How do they run?