조건부 훅(Hook)으로 위험한 명령어 차단하기 — PreToolUse 구현 및 테스트
요약
본 글은 Claude Code의 `PreToolUse` 훅(Hook)을 활용하여 AI가 실행하는 위험한 명령어(예: `rm -rf`)를 모델 외부에서 강제적으로 차단하는 방법을 다룹니다. 훅은 도구 사용 직전에 호출되어, 종료 코드와 stderr 피드백을 통해 명령 허용 여부를 결정하고 Claude에게 이유를 전달할 수 있습니다.
핵심 포인트
- PreToolUse 훅은 AI가 실행하는 명령어에 대한 외부 강제 제어 메커니즘입니다.
- 종료 코드 2는 차단과 함께 '왜 막혔는지'에 대한 상세 피드백을 Claude에게 제공합니다.
- 훅 구현 시 `set -e` 사용을 지양하고, 모든 동작(허용/차단)을 로그로 남기는 것이 중요합니다.
저는 주식회사 Joinclass에서 Claude Code를 중심으로 회사 업무의 98%를 자동화하여 운영하고 있습니다. launchd 잡이 17개, 기사 자동 공개가 하루 3채널, SNS 배포가 하루 27건에 달합니다. 사람이 개입하지 않는 시간대에 AI가 명령을 내리는 방식입니다.
그런 운영 과정에서 한번 실수를 저질렀습니다. AI가 git push --force를 자동으로 실행하여 작업 브랜치가 사라진 것입니다. 다행히 피해는 작았지만, '다음엔 rm -rf일지도 모른다'고 생각하며 손이 멈칫했습니다.
그래서 CLAUDE.md에 '절대 금지 목록(absolute prohibition list)'을 작성했습니다. 하지만 이것만으로는 효과가 미흡합니다. CLAUDE.md는 어디까지나 프롬프트일 뿐, 강제력이 없습니다. 지켜질 확률은 높지만 100%는 아닙니다. 100%로 만들려면 모델 외부에서 막아야 합니다.
그것이 Claude Code의 PreToolUse 훅(Hook)입니다. 이 글에서는 당사에서 실제로 작동하는 훅의 구현, 설정, 테스트 과정을 그대로 공개합니다. 경영 이야기는 여기까지 하고, 여기부터는 구현 이야기입니다.
Claude Code는 도구를 실행하기 직전에 설정된 명령을 호출합니다. 훅 측은 표준 입력(stdin)으로 JSON을 받고, 종료 코드(exit code)로 허용 여부를 반환합니다.
| 종료 코드 | 동작 | |
|---|---|
| 0 | 허용. 그대로 도구가 실행됨 |
| 2 | 차단(Block). stderr 내용이 Claude에게 피드백됨 |
| 기타 | 비차단 오류(Non-blocking error). stderr가 사용자에게 표시되고, 실행은 계속됨 |
중요한 것은 2의 동작입니다. 단순히 멈추는 것뿐만 아니라, 왜 멈췄는지 이유를 Claude에게 전달할 수 있다는 점입니다. 그래서 Claude는 '그럼 --force-with-lease로 할게요'라고 스스로 수정할 수 있습니다. 금지 목록을 나열하는 것보다 거부 이유를 반환하는 것이 더 현명하게 작동합니다.
표준 입력으로 들어오는 JSON은 다음과 같은 형태입니다.
{
"session_id": "b8f2...",
"transcript_path": "/Users/kyoagun/.claude/projects/.../a1b2.jsonl",
...
}
tool_input의 내용은 도구마다 다릅니다. Bash라면 command, Write/Edit이라면 file_path와 content입니다. 즉, 도구 이름으로 분기한 후 내용을 보는 것이 기본 형태가 됩니다.
당사의 .claude/hooks/deny-dangerous-command.sh 스크립트입니다. 의존성은 jq 하나뿐입니다.
#!/bin/bash
# PreToolUse: 위험한 Bash 명령을 차단합니다
# exit 0 = 허용 / exit 2 = 차단 (stderr가 Claude에게 피드백됨)
...
포인트를 세 가지로 정리했습니다.
1. set -e를 사용하지 않음
훅에서 set -e를 넣으면, grep이 불일치(종료 코드 1)를 반환하는 순간 스크립트가 중단됩니다. 중단된 종료 코드가 1이면 '비차단 오류'로 처리되어 무시되고 넘어갑니다. 즉 안전한 쪽으로 기울지 않습니다.
set -uo pipefail까지만 사용하는 것이 정답입니다. 2. 이유를 stderr에 작성
앞서 설명했듯이, exit 2의 stderr는 Claude에게 반환됩니다. '금지합니다'뿐만 아니라 대체 수단까지 적어주면, Claude가 재시도(retry)할 때 올바른 명령을 내립니다. 당사에서는 이 피드백 한 줄로 같은 차단이 두 번 일어나는 일이 거의 없어졌습니다.
3. 모든 것을 로그에 남김
허용된 경우(ALLOW)도 기록합니다. 나중에 '어떤 패턴이 과도하게 작동하고 있는지'를 계산하기 위해서입니다. 이것을 하지 않으면, 오탐지(false positive)로 인해 훅을 해제하고 싶어집니다.
훅은 프로젝트 직하의 .claude/settings.json에 등록합니다.
{
"hooks": {
"PreToolUse": [
...
}
matcher는 도구 이름의 정규 표현식입니다. Bash와 Write|Edit|MultiEdit로 나누어 별도의 스크립트로 분리했습니다. 스크립트 측에서도 tool_name
を見て早期 return 하지만, matcher로 필터링하는 것이 더 빠릅니다 (해당하지 않는 툴은 프로세스조차 실행되지 않습니다). 툴 호출마다 실행되므로, 이 차이는 체감상 크게 느껴집니다.
$CLAUDE_PROJECT_DIR
을 사용하는 것도 잊지 마세요. 상대 경로로 작성하면 Claude가 cd한 서브 디렉토리에서 호출될 때 해결하지 못하고 다운됩니다. 이건 제가 실제로 경험했습니다.
또 다른 것은, 패스 보호 측면은 다음과 같습니다. 재무 데이터나 ASIN을 포함하는 .company/를 AI가 건드리지 못하게 하자는 당사 고유의 요구사항입니다.
#!/bin/bash
# PreToolUse: 보호 대상 경로에 대한 쓰기를 차단합니다
set -uo pipefail
...
마지막의 /\.claude/hooks/가 은근히 중요합니다. 이것을 넣지 않으면, 차단된 Claude가
마지막의 /\.claude/hooks/가 은근히 중요합니다. 이것을 넣지 않으면, 차단된 Claude가
를 수정했을 때 Claude Code를 재시작합니다. 세션 중 변경 사항이 반영되지 않아 '테스트는 통과하는데 실제 환경에서 멈추지 않는다'는 혼란의 원인이 될 수 있습니다. 현재 로드된 훅은 /hooks로 확인할 수 있습니다.
6. 테스트를 주기적 실행에 포함시키기
저희 회사는 launchd의 일일 작업(daily job)에서 test-deny.sh를 호출하여, 실패하면 Slack으로 알림을 보냅니다. 훅은 '평소에는 아무 일도 일어나지 않는' 구조이기 때문에, 고장 났다는 것을 알아차릴 수 있는 별도의 수단을 마련해야 합니다.
<!-- ~/Library/LaunchAgents/com.joinclass.hook-test.plist -->
<key>ProgramArguments</key>
<array>
...
참고로 macOS에서는 cron이 전체 디스크 접근 문제로 인해 높은 확률로 작동하지 않습니다. 저희 회사는 한때 cron 작업들이 모두 실패하여 launchd로 완전히 전환했습니다.
이 시스템을 도입한 후, 제가 AI의 출력을 감시하는 시간은 거의 0에 가까워졌습니다. 아침 5분 동안 요약본만 보고 승인 버튼을 누르는 것으로 충분합니다.
이 메커니즘을 'AI의 능력을 제한하는 것'이라고 생각하면 만들 의욕이 생기지 않습니다. 실제로는 그 반대입니다. 안심하고 위임하기 위한 시스템이죠. 멈추게 하는 장치가 있기 때문에, 사람이 보고 있지 않은 시간대에도 실행을 맡길 수 있습니다. AI는 실행에 사용하고, 판단은 인간이 갖습니다. — 이 경계선을 프롬프트가 아닌 종료 코드(exit code)로 그리는 것. 그것이 PreToolUse 훅의 역할이라고 생각합니다.
우선 git push --force 딱 한 줄만 넣어보세요. 저처럼 브랜치를 날려버리기 전에요.
본 기사의 내용을 더욱 체계적으로 정리한 책을 출간했습니다.
『Claude Code 완전 자동화 바이블』
Hooks, launchd, cron을 조합하여 업무를 어디까지 자동으로 돌릴 수 있는지 구현 기반으로 해설하고 있습니다. 본 기사의 훅은 그 일부입니다.
권한 설계나 시크릿 관리까지 깊이 다루고 싶으신 분들은 **『기업을 위한 Claude Code 보안 가이드』**도 함께 참고해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기