docs: add example pre-exec hooks
Includes three ready-to-use safety hooks: - safe-git.sh: Blocks protected branch pushes and force pushes - safe-db.sh: Blocks write operations on remote/production databases - safe-rm.sh: Blocks dangerous file deletions (rm -rf /, etc)
This commit is contained in:
parent
663dd5a44e
commit
4c8a51785c
71
examples/pre-exec-hooks/README.md
Normal file
71
examples/pre-exec-hooks/README.md
Normal file
@ -0,0 +1,71 @@
|
||||
# Pre-Exec Hooks Examples
|
||||
|
||||
Example shell scripts that intercept Bash/exec tool calls before they run.
|
||||
|
||||
## Installation
|
||||
|
||||
Copy hooks to your workspace:
|
||||
|
||||
```bash
|
||||
mkdir -p .clawdbot/hooks
|
||||
cp safe-git.sh safe-db.sh .clawdbot/hooks/
|
||||
chmod +x .clawdbot/hooks/*.sh
|
||||
```
|
||||
|
||||
## Included Hooks
|
||||
|
||||
### safe-git.sh
|
||||
|
||||
Protects your git workflow:
|
||||
- ❌ Blocks force pushes (`--force`, `-f`)
|
||||
- ❌ Blocks pushes to protected branches (main, develop, staging, production)
|
||||
- ❌ Blocks remote modifications
|
||||
- ✅ Allows normal git operations
|
||||
|
||||
### safe-db.sh
|
||||
|
||||
Protects production databases:
|
||||
- ❌ Blocks INSERT, UPDATE, DELETE, DROP on remote hosts
|
||||
- ❌ Blocks migrations/seeds targeting staging/production
|
||||
- ✅ Allows SELECT queries on remote DBs
|
||||
- ✅ Allows all operations on localhost
|
||||
|
||||
### safe-rm.sh
|
||||
|
||||
Prevents catastrophic deletions:
|
||||
- ❌ Blocks `rm -rf /`
|
||||
- ❌ Blocks `rm` on system directories
|
||||
- ❌ Blocks `rm -rf *` (wildcard)
|
||||
- ✅ Allows normal file deletion
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Test safe-git.sh
|
||||
echo '{"tool_name":"exec","tool_input":{"command":"git push origin main"}}' | ./safe-git.sh
|
||||
# → {"decision": "deny", "reason": "🚫 Pushing to protected branches is blocked..."}
|
||||
|
||||
# Test safe-db.sh
|
||||
echo '{"tool_name":"exec","tool_input":{"command":"psql -h prod.db.com -c \"DROP TABLE users\""}}' | ./safe-db.sh
|
||||
# → {"decision": "deny", "reason": "🚫 Non-SELECT operations on remote databases are blocked."}
|
||||
```
|
||||
|
||||
## Writing Your Own
|
||||
|
||||
See [Pre-Exec Hooks Documentation](../../docs/tools/pre-exec-hooks.md) for the full protocol.
|
||||
|
||||
Basic template:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
INPUT=$(cat)
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
# Your logic here
|
||||
if should_block "$COMMAND"; then
|
||||
echo '{"decision": "deny", "reason": "Your message here"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"decision": "approve"}'
|
||||
```
|
||||
55
examples/pre-exec-hooks/safe-db.sh
Executable file
55
examples/pre-exec-hooks/safe-db.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# Clawdbot PreToolUse Hook: STRICT read-only for non-local databases
|
||||
# Based on Claude Code hooks in ~/code/yieldnest/yieldnest-api/.claude/hooks/
|
||||
#
|
||||
# Only SELECT allowed on remote DBs - everything else blocked
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
# Only process Bash/exec tools
|
||||
[[ "$TOOL" != "Bash" && "$TOOL" != "exec" ]] && echo '{"decision": "approve"}' && exit 0
|
||||
|
||||
# Known non-local DB patterns (customize as needed)
|
||||
REMOTE_HOSTS="gondola|maglev|railway|\.up\.railway\.app|staging|prod|amazonaws\.com|azure|supabase|neon\.tech|planetscale"
|
||||
|
||||
# Check if command involves any remote database
|
||||
if echo "$COMMAND" | grep -qiE "($REMOTE_HOSTS)"; then
|
||||
|
||||
# Block any non-SELECT SQL operations
|
||||
if echo "$COMMAND" | grep -qiE "(INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE|GRANT|REVOKE|EXECUTE|COPY|VACUUM|ANALYZE|REINDEX|REFRESH|LOCK|COMMENT)\s"; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Write operation blocked on remote DB. Only SELECT queries allowed on staging/prod. Use local DB for writes."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block psql -c with non-SELECT
|
||||
if echo "$COMMAND" | grep -qiE "psql.*-c\s*['\"]?\s*(INSERT|UPDATE|DELETE|DROP|TRUNCATE|ALTER|CREATE)"; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Write operation blocked on remote DB. Only SELECT allowed."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block piped writes
|
||||
if echo "$COMMAND" | grep -qiE "(echo|cat|printf).*\|.*psql" && echo "$COMMAND" | grep -qiE "(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER)"; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Piped write operation blocked on remote DB."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Block ORM/migration commands on non-local
|
||||
if echo "$COMMAND" | grep -qiE "(prisma|typeorm|knex|sequelize|drizzle|migrate|seed|db:push|db:seed|sync)"; then
|
||||
if echo "$COMMAND" | grep -qiE "($REMOTE_HOSTS|APP_ENV.*(prod|staging)|NODE_ENV.*(prod|staging))"; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Migrations/seeds blocked on remote DB. Only run on local."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Block connection strings pointing to remote with potential writes
|
||||
if echo "$COMMAND" | grep -qiE "(DATABASE_URL|postgres://|postgresql://|mysql://|mongodb).*($REMOTE_HOSTS)"; then
|
||||
if echo "$COMMAND" | grep -qiE "(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|migrate|seed|push|sync)"; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Non-read operation blocked on remote DB."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo '{"decision": "approve"}'
|
||||
51
examples/pre-exec-hooks/safe-git.sh
Executable file
51
examples/pre-exec-hooks/safe-git.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# Clawdbot PreToolUse Hook: Prevent pushes to protected branches
|
||||
# Based on Claude Code hooks in ~/code/yieldnest/yieldnest-api/.claude/hooks/
|
||||
#
|
||||
# Allows: all git operations EXCEPT pushing to protected branches
|
||||
# Protected branches: develop, production, staging, main
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
# Only process Bash/exec tools
|
||||
[[ "$TOOL" != "Bash" && "$TOOL" != "exec" ]] && echo '{"decision": "approve"}' && exit 0
|
||||
|
||||
# Block git remote modifications
|
||||
if echo "$COMMAND" | grep -qE 'git\s+remote\s+(add|remove|rm|set-url|rename)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Modifying git remotes is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block force push operations
|
||||
# Match -f or --force as standalone arguments (preceded by whitespace, not as part of branch name)
|
||||
if echo "$COMMAND" | grep -qE 'git\s+push\s+(-f\s|--force\s|-f$|--force$)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Force push is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
if echo "$COMMAND" | grep -qE 'git\s+push\s+\S+\s+(-f\s|--force\s|-f$|--force$)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Force push is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
if echo "$COMMAND" | grep -qE 'git\s+push\s+\S+\s+\S+\s+(-f|--force)(\s|$)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Force push is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block pushes to protected branches
|
||||
if echo "$COMMAND" | grep -qE '(^|\s|;|\||&&)git\s+push'; then
|
||||
# Check for protected branch names in the push command
|
||||
if echo "$COMMAND" | grep -qE 'git\s+push\s+[^\s]+\s+(develop|production|staging|main)(\s|$|:)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Pushing to protected branches (develop/production/staging/main) is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for push to origin with protected branch
|
||||
if echo "$COMMAND" | grep -qE 'git\s+push\s+origin\s+(develop|production|staging|main)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 Pushing to protected branches (develop/production/staging/main) is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo '{"decision": "approve"}'
|
||||
47
examples/pre-exec-hooks/safe-rm.sh
Executable file
47
examples/pre-exec-hooks/safe-rm.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# Clawdbot PreToolUse Hook: Prevent dangerous rm operations
|
||||
#
|
||||
# Blocks:
|
||||
# - rm -rf /
|
||||
# - rm on home directory
|
||||
# - rm on common system directories
|
||||
# - rm without -i on important directories
|
||||
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.tool_name // empty')
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
# Only process Bash/exec tools
|
||||
[[ "$TOOL" != "Bash" && "$TOOL" != "exec" ]] && echo '{"decision": "approve"}' && exit 0
|
||||
|
||||
# Skip if not an rm command
|
||||
if ! echo "$COMMAND" | grep -qE '(^|\s|;|\||&&)rm\s'; then
|
||||
echo '{"decision": "approve"}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block rm -rf /
|
||||
if echo "$COMMAND" | grep -qE 'rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+/?(\s|$|;|\||&&)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 rm -rf / is blocked. Use trash instead for safe deletion."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block rm on home directory
|
||||
if echo "$COMMAND" | grep -qE 'rm\s+.*(\$HOME|~|/home/[^/]+)\s*/?(\s|$|;|\||&&)'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 rm on home directory is blocked. Use trash instead."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block rm on system directories
|
||||
if echo "$COMMAND" | grep -qE 'rm\s+.*(^|\s)/(usr|bin|sbin|etc|var|opt|lib|System|Applications)\s*/?'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 rm on system directories is blocked."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Block rm -rf without explicit path (could be dangerous)
|
||||
if echo "$COMMAND" | grep -qE 'rm\s+.*-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s*\*'; then
|
||||
echo '{"decision": "deny", "reason": "🚫 rm -rf * is too dangerous. Be more specific or use trash."}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo '{"decision": "approve"}'
|
||||
Loading…
Reference in New Issue
Block a user