Claude Code Hook Recipe: Block Dangerous Bash with PreToolUse
A PreToolUse Bash hook that intercepts destructive commands like rm -rf, fork bombs, and curl|sh before the AI executes them—a last line of defense that holds even against --dangerously-skip-permissions.
# 目標:在 Claude Code 執行任何 Bash 指令「之前」攔截毀滅性命令
# 放到 .claude/settings.json(專案)或 ~/.claude/settings.json(全域,建議全域,全專案都保護)。
# 機制:PreToolUse hook 在工具執行前觸發;hook 從 stdin 收 JSON,
# 讀 tool_input.command 去比對危險 pattern;命中就 exit 2 → Claude Code 取消該動作。
# 這層連 --dangerously-skip-permissions 也擋得住,是真正的最後防線。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash {{PROJECT_DIR}}/.claude/hooks/block-dangerous-commands.sh"
}
]
}
]
}
}
# ---- .claude/hooks/block-dangerous-commands.sh ----
# chmod +x 後使用。命中危險 pattern:印理由到 stderr 並 exit 2(= 拒絕,AI 會看到原因)。
#!/usr/bin/env bash
set -euo pipefail
cmd=$(jq -r '.tool_input.command // empty')
[ -z "$cmd" ] && exit 0
deny() { echo "🛑 已被安全 hook 攔截:$1" >&2; echo " 指令:$cmd" >&2; exit 2; }
# 安全等級:critical(只擋毀系統)/ high(再加強推、reset --hard)/ strict(最謹慎)
SAFETY_LEVEL="{{SAFETY_LEVEL}}" # 建議先用 high
# --- critical:任何等級都擋 ---
echo "$cmd" | grep -Eq 'rm[[:space:]]+(-[a-zA-Z]*[rR][a-zA-Z]*[fF]|-[a-zA-Z]*[fF][a-zA-Z]*[rR])([[:space:]]|$)' && deny "遞迴強制刪除(rm -rf)"
echo "$cmd" | grep -Eq 'rm[[:space:]]+.*[[:space:]](/|~|\$HOME|/\*)([[:space:]]|$)' && deny "對根目錄/家目錄刪除"
echo "$cmd" | grep -Eq ':\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:' && deny "fork bomb"
echo "$cmd" | grep -Eq '(curl|wget)[^|]*\|[[:space:]]*(sudo[[:space:]]+)?(ba)?sh' && deny "下載直接執行(curl|sh / wget|sh)"
echo "$cmd" | grep -Eq '(mkfs|>[[:space:]]*/dev/sd|dd[[:space:]]+if=.*of=/dev/)' && deny "直接寫入/格式化磁碟裝置"
echo "$cmd" | grep -Eq 'chmod[[:space:]]+-R[[:space:]]+777[[:space:]]+/' && deny "對根目錄 chmod 777"
# --- high:再加常見「無法復原」操作 ---
if [ "$SAFETY_LEVEL" = "high" ] || [ "$SAFETY_LEVEL" = "strict" ]; then
echo "$cmd" | grep -Eq 'git[[:space:]]+push[[:space:]]+.*(--force|-f)([[:space:]]|$)' && deny "git 強制推送(可能覆蓋遠端歷史)"
echo "$cmd" | grep -Eq 'git[[:space:]]+reset[[:space:]]+--hard' && deny "git reset --hard(丟棄未提交變更)"
echo "$cmd" | grep -Eq 'git[[:space:]]+clean[[:space:]]+-[a-zA-Z]*[fd]' && deny "git clean -fd(刪除未追蹤檔案)"
fi
# --- strict:連批次刪除、sudo rm 都提醒 ---
if [ "$SAFETY_LEVEL" = "strict" ]; then
echo "$cmd" | grep -Eq 'sudo[[:space:]]+rm' && deny "sudo rm(提權刪除)"
echo "$cmd" | grep -Eq 'find[[:space:]]+.*-delete' && deny "find ... -delete(批次刪除)"
fi
exit 0 # 沒命中 → 放行See what this prompt actually produces without leaving the site (live AI run, 1 credit).
Don't just copy-paste — download and drop it at ~/.claude/skills/hook-block-dangerous-bash-pretooluse-guardrail/SKILL.md and every future session can use it automatically.
mkdir -p ~/.claude/skills/hook-block-dangerous-bash-pretooluse-guardrail && mv ~/Downloads/SKILL.md ~/.claude/skills/hook-block-dangerous-bash-pretooluse-guardrail/SKILL.mdNew-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\skills\hook-block-dangerous-bash-pretooluse-guardrail" | Out-Null; Move-Item "$env:USERPROFILE\Downloads\SKILL.md" "$env:USERPROFILE\.claude\skills\hook-block-dangerous-bash-pretooluse-guardrail\SKILL.md"## What This Is / What Problem It Solves You're willing to let Claude Code run bash automatically to boost your speed, but there's one category of command that, once executed, can't be undone: `rm -rf /`, `rm -rf ~`, fork bombs, `curl http://… | sh`, `dd if=… of=/dev/sda`. Requiring manual approval for everything is exhausting and easy to accidentally click through, while `--dangerously-skip-permissions` (YOLO mode) removes the brakes entirely. This PreToolUse hook adds a genuine last line of defense: before any bash command the AI sends is executed, it scans for dangerous patterns and rejects with an explanation on a match—crucially, this layer holds even against `--dangerously-skip-permissions`, because hooks are enforced by the harness itself, not governed by the AI's permission flags. ## Why This Source Is Worth Using This recipe is adapted from the `block-dangerous-commands` hook in karanb192/claude-code-hooks (MIT), built exactly for this scenario: a PreToolUse hook scoped to the `Bash` matcher, reading `tool_input.command` from the JSON on stdin, and exiting with code 2 to reject on a match—with critical / high / strict tiered safety levels. I rewrote its core logic in pure bash (the original project is a Node script) so it works directly even without Node installed, while keeping the tiered design so you can start with the more permissive `critical` level and move up to `high` / `strict` once your team is used to it. ## How to Use It (Steps) 1. Merge the JSON into `~/.claude/settings.json` (recommended globally, so every project is protected). 2. Create `.claude/hooks/block-dangerous-commands.sh`, paste in the script, and `chmod +x` it. 3. Make sure `jq` is installed (used to parse the stdin JSON). 4. Set `{{SAFETY_LEVEL}}` to `high` (recommended starting point). 5. Test it: have the AI try to run a harmless `rm -rf /tmp/this-should-be-blocked-xyz`; you should see the hook reject it, with the AI receiving the reason and switching to a safer approach. ## Key Points and When to Use - **Exit code 2 means 'reject'**: a PreToolUse hook uses exit code 2 to block an action and returns the stderr content to the AI as the reason; exit 0 allows it through. This is completely different from PostToolUse (which can't stop an action that already ran). - **Patterns should be conservative—better to miss than to over-block**: overly aggressive regexes will also block normal commands and stall the AI. Start by blocking clearly destructive commands, then tighten based on your team's habits. - **It's a safety net, not a replacement**: version control and backups for important repos are still recommended; this hook guards against 'slips and loss of control,' not malicious attacks. - **When to use**: any time you plan to turn on auto-approve / YOLO mode, or let the AI run shell commands in an environment containing real data, you should install this layer first. 📎 Source: karanb192/claude-code-hooks (author: Karan Bansal, MIT license) — this piece is adapted from its block-dangerous-commands hook (including the critical/high/strict tiered design), rewritten in pure bash with explanations in Traditional Chinese; see the link above for the original.
[PROJECT_DIR]hook 腳本所在路徑;放全域時可改用 ~/.claude/hooks/... 的絕對路徑
[SAFETY_LEVEL]安全等級:critical(只擋毀系統)/high(加 git 強推、reset --hard,推薦)/strict(最謹慎)
填下面的欄位,上方 prompt 會即時替換 [方括號] 內容。填好後按「複製組好的 prompt」直接丟進工具。
# 目標:在 Claude Code 執行任何 Bash 指令「之前」攔截毀滅性命令
# 放到 .claude/settings.json(專案)或 ~/.claude/settings.json(全域,建議全域,全專案都保護)。
# 機制:PreToolUse hook 在工具執行前觸發;hook 從 stdin 收 JSON,
# 讀 tool_input.command 去比對危險 pattern;命中就 exit 2 → Claude Code 取消該動作。
# 這層連 --dangerously-skip-permissions 也擋得住,是真正的最後防線。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash {{PROJECT_DIR}}/.claude/hooks/block-dangerous-commands.sh"
}
]
}
]
}
}
# ---- .claude/hooks/block-dangerous-commands.sh ----
# chmod +x 後使用。命中危險 pattern:印理由到 stderr 並 exit 2(= 拒絕,AI 會看到原因)。
#!/usr/bin/env bash
set -euo pipefail
cmd=$(jq -r '.tool_input.command // empty')
[ -z "$cmd" ] && exit 0
deny() { echo "🛑 已被安全 hook 攔截:$1" >&2; echo " 指令:$cmd" >&2; exit 2; }
# 安全等級:critical(只擋毀系統)/ high(再加強推、reset --hard)/ strict(最謹慎)
SAFETY_LEVEL="{{SAFETY_LEVEL}}" # 建議先用 high
# --- critical:任何等級都擋 ---
echo "$cmd" | grep -Eq 'rm[[:space:]]+(-[a-zA-Z]*[rR][a-zA-Z]*[fF]|-[a-zA-Z]*[fF][a-zA-Z]*[rR])([[:space:]]|$)' && deny "遞迴強制刪除(rm -rf)"
echo "$cmd" | grep -Eq 'rm[[:space:]]+.*[[:space:]](/|~|\$HOME|/\*)([[:space:]]|$)' && deny "對根目錄/家目錄刪除"
echo "$cmd" | grep -Eq ':\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:' && deny "fork bomb"
echo "$cmd" | grep -Eq '(curl|wget)[^|]*\|[[:space:]]*(sudo[[:space:]]+)?(ba)?sh' && deny "下載直接執行(curl|sh / wget|sh)"
echo "$cmd" | grep -Eq '(mkfs|>[[:space:]]*/dev/sd|dd[[:space:]]+if=.*of=/dev/)' && deny "直接寫入/格式化磁碟裝置"
echo "$cmd" | grep -Eq 'chmod[[:space:]]+-R[[:space:]]+777[[:space:]]+/' && deny "對根目錄 chmod 777"
# --- high:再加常見「無法復原」操作 ---
if [ "$SAFETY_LEVEL" = "high" ] || [ "$SAFETY_LEVEL" = "strict" ]; then
echo "$cmd" | grep -Eq 'git[[:space:]]+push[[:space:]]+.*(--force|-f)([[:space:]]|$)' && deny "git 強制推送(可能覆蓋遠端歷史)"
echo "$cmd" | grep -Eq 'git[[:space:]]+reset[[:space:]]+--hard' && deny "git reset --hard(丟棄未提交變更)"
echo "$cmd" | grep -Eq 'git[[:space:]]+clean[[:space:]]+-[a-zA-Z]*[fd]' && deny "git clean -fd(刪除未追蹤檔案)"
fi
# --- strict:連批次刪除、sudo rm 都提醒 ---
if [ "$SAFETY_LEVEL" = "strict" ]; then
echo "$cmd" | grep -Eq 'sudo[[:space:]]+rm' && deny "sudo rm(提權刪除)"
echo "$cmd" | grep -Eq 'find[[:space:]]+.*-delete' && deny "find ... -delete(批次刪除)"
fi
exit 0 # 沒命中 → 放行Suno Engineer's Mindset: 4 Steps to a Song That Doesn't Sound Like AI
A studio engineer's breakdown of Suno's fatal weaknesses (fried vocals, high-frequency artifacts), plus a 4-step DAW workflow and a Suno Studio cleanup prompt.
5 Claude Weekly Workflows That Stuck After 6 Months
Proposal generator / meeting processor / content repurposer / Friday review / shutdown reset — out of 40 I tried, only these 5 survived, each saving 30+ minutes per run.