GrepBoard/매뉴얼
47 편 ·

Claude Code 훅으로 위험한 명령 막기

·Claude Code 2.1.302 · Ubuntu 24.04 · jq 1.7·8 단계

PreToolUse 훅으로 Claude Code 가 실행하려는 Bash 명령을 먼저 검사하고, rm -rf 나 강제 푸시는 종료 코드 2 로 막는 설정 파일과 스크립트입니다.

훅(hook)은 Claude Code 가 도구를 쓰기 전후 같은 정해진 시점에 실행하는 사용자 명령입니다. 여기서는 PreToolUse 훅으로 Bash 명령을 실행 전에 검사해 막습니다.

[1] 프로젝트 폴더에 훅 스크립트를 둘 디렉터리를 만듭니다.

console
$ mkdir -p .claude/hooks

[2] .claude/settings.json 에 훅을 등록합니다. 구조는 이벤트(PreToolUse) → 매처 그룹(matcher) → 핸들러 목록(hooks) 의 세 단계입니다. matcher 는 도구 이름에 걸리며 "Bash" 는 Bash 도구만 가리킵니다.

.claude/settings.json
{"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 로 꺼냅니다.

.claude/hooks/block-dangerous.sh
#!/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 을 직접 파이프로 넣어 종료 코드를 확인합니다.

console
$ 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 여도 차단).

output
{  "hookSpecificOutput": {    "hookEventName": "PreToolUse",    "permissionDecision": "deny",    "permissionDecisionReason": "Destructive command blocked by hook"  }}

[7] 세션에서 실제로 막히는지 봅니다. 프로젝트 폴더에서 claude -p 로 위험한 명령을 요청합니다(./cache 에는 시험용 파일 하나를 두었습니다). --allowedTools Bash 로 미리 허용해도 훅의 차단이 이깁니다.

console
$ 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.txt

Claude 는 stderr 의 Blocked: 위험한 명령입니다 — rm -rf ./cache 를 받고 실행하지 못했다고 답했으며, 파일은 그대로 남았습니다.

[8] 세션 안에서 /hooks 를 입력하면 설정된 훅을 이벤트별로 묶어 보여 줍니다. 등록한 훅이 PreToolUse 아래 보이는지 확인합니다.

다른 이벤트도 같은 구조로 등록합니다. 파일 수정 뒤 포매터를 돌리는 PostToolUse, 프롬프트를 검사하는 UserPromptSubmit, 세션 시작 때 맥락을 넣는 SessionStart, 입력 대기를 알리는 Notification 이 자주 쓰입니다. 전체 목록은 훅 레퍼런스를 봅니다.

#claude-code#hooks