Claude Code 훅으로 위험한 명령 막기
PreToolUse 훅으로 Claude Code 가 실행하려는 Bash 명령을 먼저 검사하고, rm -rf 나 강제 푸시는 종료 코드 2 로 막는 설정 파일과 스크립트입니다.
훅(hook)은 Claude Code 가 도구를 쓰기 전후 같은 정해진 시점에 실행하는 사용자 명령입니다. 여기서는 PreToolUse 훅으로 Bash 명령을 실행 전에 검사해 막습니다.
[1] 프로젝트 폴더에 훅 스크립트를 둘 디렉터리를 만듭니다.
$ mkdir -p .claude/hooks[2] .claude/settings.json 에 훅을 등록합니다. 구조는 이벤트(PreToolUse) → 매처 그룹(matcher) → 핸들러 목록(hooks) 의 세 단계입니다. matcher 는 도구 이름에 걸리며 "Bash" 는 Bash 도구만 가리킵니다.
{"hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.sh" } ] } ]}}[3] 훅 스크립트를 만듭니다. Claude Code 는 훅 입력을 JSON 으로 표준 입력에 넘깁니다. Bash 호출이면 실행할 명령이 tool_input.command 에 들어 있으므로 jq 로 꺼냅니다.
#!/bin/bashcommand=$(jq -r '.tool_input.command // empty') if echo "$command" | grep -Eq 'rm +(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r) |git +push +.*--force|DROP +TABLE'; then echo "Blocked: 위험한 명령입니다 — $command" >&2 exit 2fiexit 0입력의 tool_input.command 가 실행할 명령입니다.
[4] 실행 권한을 주고, Claude Code 없이 JSON 을 직접 파이프로 넣어 종료 코드를 확인합니다.
$ chmod +x .claude/hooks/block-dangerous.sh$ echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /srv/app/cache"}}' | .claude/hooks/block-dangerous.sh; echo "exit=$?"Blocked: 위험한 명령입니다 — rm -rf /srv/app/cacheexit=2$ echo '{"tool_name":"Bash","tool_input":{"command":"git push origin main --force"}}' | .claude/hooks/block-dangerous.sh; echo "exit=$?"Blocked: 위험한 명령입니다 — git push origin main --forceexit=2$ echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' | .claude/hooks/block-dangerous.sh; echo "exit=$?"exit=0[5] 막는 규칙은 종료 코드로 정해집니다.
| 종료 코드 | 결과 |
|---|---|
0 | 통과. 결정을 내리지 않으므로 평소 권한 확인 흐름을 그대로 탑니다 |
2 | 차단. 도구 호출이 실행되지 않고 stderr 내용이 Claude 에게 전달됩니다 |
그 밖 (1 등) | 차단하지 않는 오류. 명령은 그대로 실행되고 대화 기록에 hook error 알림만 남습니다 |
[6] 종료 코드 대신 JSON 으로 결정을 내릴 수도 있습니다. 이때는 exit 0 으로 끝내고 표준 출력에 아래 JSON 을 냅니다. permissionDecision 은 allow · deny · ask · defer 중 하나입니다. 한 훅에서는 한 방식만 씁니다(exit 2 는 JSON 이 allow 여도 차단).
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Destructive command blocked by hook" }}[7] 세션에서 실제로 막히는지 봅니다. 프로젝트 폴더에서 claude -p 로 위험한 명령을 요청합니다(./cache 에는 시험용 파일 하나를 두었습니다). --allowedTools Bash 로 미리 허용해도 훅의 차단이 이깁니다.
$ ls cachea.txt$ claude -p "Run this exact shell command and report the result: rm -rf ./cache" --max-turns 3 --output-format json --allowedTools Bash$ ls cachea.txtClaude 는 stderr 의 Blocked: 위험한 명령입니다 — rm -rf ./cache 를 받고 실행하지 못했다고 답했으며, 파일은 그대로 남았습니다.
[8] 세션 안에서 /hooks 를 입력하면 설정된 훅을 이벤트별로 묶어 보여 줍니다. 등록한 훅이 PreToolUse 아래 보이는지 확인합니다.
다른 이벤트도 같은 구조로 등록합니다. 파일 수정 뒤 포매터를 돌리는 PostToolUse, 프롬프트를 검사하는 UserPromptSubmit, 세션 시작 때 맥락을 넣는 SessionStart, 입력 대기를 알리는 Notification 이 자주 쓰입니다. 전체 목록은 훅 레퍼런스를 봅니다.