Claude Code Hooks 설정: 위험한 Bash 명령을 실행 전에 차단하는 방법
Claude Code에 “위험한 명령은 실행하지 마”라고 적어 두는 것과 시스템 수준에서 실행을 차단하는 것은 다릅니다. 프롬프트는 작업 방향을 알려 주지만, 긴 세션이나 복잡한 요청에서도 항상 같은 정책을 강제하는 보안 경계는 아닙니다.
Claude Code Hooks를 사용하면 도구 호출 전후에 사용자가 만든 명령을 실행할 수 있습니다. 그중 PreToolUse는 Bash 명령이 실행되기 전에 입력을 검사하고, 정책에 맞지 않으면 실제 실행을 막을 수 있습니다.
Hooks의 동작 방식
Hook은 Claude Code의 특정 이벤트에 연결되는 자동화 명령입니다. Claude가 Bash 도구를 호출하려 하면 PreToolUse Hook에 도구 이름과 입력값이 JSON으로 전달됩니다. Hook은 이를 검사한 뒤 종료 코드나 구조화된 JSON으로 허용 여부를 돌려줍니다.
| 수단 | 역할 | 특징 |
|---|---|---|
CLAUDE.md 지침 |
작업 방식 설명 | 자연어 지침이며 프로젝트 맥락 제공에 적합 |
| 권한 규칙 | 도구·명령 패턴의 허용과 거부 | 정해진 규칙을 설정으로 관리 |
PreToolUse Hook |
도구 입력을 실행 직전에 검사 | 자체 코드로 세밀한 정책 구현 가능 |
PostToolUse Hook |
실행 결과를 받은 뒤 처리 | 포맷팅, 검사, 감사 로그에 적합 |
PreToolUse는 권한 모드 검사보다 먼저 실행되며, 공식 문서에 따르면 --dangerously-skip-permissions에서도 deny 결정은 적용됩니다. Hook은 완전한 샌드박스가 아니므로 실제 권한 통제와 함께 사용해야 합니다.
프로젝트 Hook 설정
프로젝트 루트에 .claude/settings.json을 만듭니다. 이 파일은 해당 프로젝트에만 적용되고 저장소에 공유할 수 있습니다. 개인 PC에서만 사용할 규칙은 .claude/settings.local.json, 모든 프로젝트에 적용할 규칙은 ~/.claude/settings.json에 둘 수 있습니다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous.py"
}
]
}
]
}
}
matcher의 Bash는 Bash 도구에만 적용한다는 뜻입니다. $CLAUDE_PROJECT_DIR를 사용하면 하위 디렉터리에서도 프로젝트 기준으로 스크립트를 찾습니다. JSON에는 주석과 후행 쉼표를 넣을 수 없습니다.
위험 명령 차단 스크립트
.claude/hooks/block-dangerous.py를 다음처럼 작성합니다.
import json
import re
import sys
def main() -> int:
event = json.load(sys.stdin)
command = event.get("tool_input", {}).get("command", "")
rules = [
(
r"(^|[;&|]\s*)rm\s+-rf\s+(--\s+)?(/|~|\$HOME)(\s|$)",
"루트 또는 홈 디렉터리 삭제",
),
(
r"\bgit\s+reset\s+--hard\b",
"작업 트리 변경을 지우는 git reset --hard",
),
(
r"\bgit\s+push\b[^\n]*(--force-with-lease|--force|-f)(\s|$)",
"원격 이력을 바꾸는 강제 push",
),
]
for pattern, reason in rules:
if re.search(pattern, command, flags=re.IGNORECASE):
print(
f"Blocked: {reason} 명령은 프로젝트 정책상 허용되지 않습니다.",
file=sys.stderr,
)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())
Claude Code는 Hook의 표준 입력에 다음과 비슷한 JSON을 전달합니다.
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "git push origin main --force"
}
}
종료 코드 2는 호출을 차단하고 표준 오류의 문장을 Claude에게 피드백으로 전달합니다. 코드 0은 Hook이 이의를 제기하지 않았다는 뜻이며 기존 권한 검사는 계속됩니다.
직접 테스트하기
실제 Claude 세션에 적용하기 전에 안전한 입력을 파이프로 전달해 결과를 확인합니다.
echo '{"tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
| python3 .claude/hooks/block-dangerous.py
echo $? # 0
echo '{"tool_name":"Bash","tool_input":{"command":"git push origin main --force"}}' \
| python3 .claude/hooks/block-dangerous.py
echo $? # 2
첫 명령은 통과하고 둘째는 차단되어야 합니다. 배포 전에는 정상 입력, 차단 입력, 빈 입력 필드도 테스트합니다.
어떤 명령을 차단해야 할까
블랙리스트가 넓으면 정상 작업까지 막습니다. 위 예제는 루트와 홈 삭제만 막습니다. 다음 항목을 프로젝트 특성에 맞게 추가할 수 있습니다.
- 운영 배포 명령과 프로덕션 데이터베이스 접속
DROP TABLE, 대량DELETE등 파괴적 SQL- 보호 브랜치 강제 push
- 비밀 파일 출력이나 외부 업로드
- 승인되지 않은 클라우드 리소스 삭제
정규식만으로 별칭, 변수 치환, 중첩 셸을 모두 분석하기는 어렵습니다. 중요한 정책은 IAM, DB 권한, 보호 브랜치에서도 막아야 합니다.
자주 하는 실수
첫째, 저장소에서 받은 Hook 스크립트를 검토하지 않고 실행합니다. Hook도 현재 사용자 권한으로 실행되는 코드이므로 외부 프로젝트의 .claude/settings.json과 스크립트를 먼저 확인해야 합니다.
둘째, API 키나 토큰을 Hook 출력에 기록합니다. 디버그 로그와 세션 기록에 남을 수 있으므로 비밀값은 출력하지 않습니다.
셋째, 종료 코드 2와 구조화된 JSON을 함께 사용합니다. 공식 문서는 코드 2로 차단할 때 JSON 출력을 섞지 않도록 안내합니다. 세밀한 제어 JSON을 사용하려면 코드 0으로 종료해야 합니다.
넷째, Hook 하나만으로 안전이 완성됐다고 생각합니다. Hook은 실수를 줄이는 방어선이지 운영체제나 컨테이너 격리를 대신하지 않습니다.
핵심 요약
PreToolUse는 Bash 명령이 실행되기 전에 입력을 검사합니다.- 프로젝트 Hook은
.claude/settings.json에 등록합니다. - JSON 입력에서
tool_input.command를 읽고 종료 코드2로 호출을 차단할 수 있습니다. - 차단 이유를 표준 오류에 쓰면 Claude가 다른 방법을 선택하는 데 활용합니다.
- Hook 스크립트 자체를 검토하고, 실제 권한 통제와 함께 다층 방어로 사용합니다.
Claude Code의 자율성이 높아질수록 “잘 요청하는 법”과 “실행 가능한 범위를 제한하는 법”이 함께 필요합니다. 반복해서 금지 문구를 입력하기보다 실행 직전의 정책을 코드로 고정하면 팀 전체가 같은 안전 기준을 적용할 수 있습니다.
함께 읽으면 좋은 글:
최종 확인: 2026-07-30
'AI > Claude' 카테고리의 다른 글
| 클로드 Fable 5, 결국 Pro 사용자만 뒤통수 맞았다: $100 크레딧의 진짜 의미 (0) | 2026.07.21 |
|---|---|
| Claude API 폴백·재시도 전략, Spring Boot로 구현하기 (0) | 2026.07.13 |
| Claude Fable 5·Mythos 5, 19일 셧다운이 남긴 것 (0) | 2026.07.09 |