본문으로 건너뛰기
AICosmus

Where tech meets the everyday — AI, fintech, swimming, and cars.

AICosmus

Where tech meets the everyday — AI, fintech, swimming, and cars.

  • 홈
  • IT기술
    • RAG
    • GRPC
    • Kotlin
    • LLM
    • 금융 IT
    • 에이전트
    • 제로Trust
    • 자동화
  • About
    • Contact
    • Terms of Service
    • Disclaimer
    • Privacy – Policy
  • 홈
  • IT기술
    • RAG
    • GRPC
    • Kotlin
    • LLM
    • 금융 IT
    • 에이전트
    • 제로Trust
    • 자동화
  • About
    • Contact
    • Terms of Service
    • Disclaimer
    • Privacy – Policy
닫기

검색

Claude Code 권한 설정 3중 방어 체계 일러스트
IT기술

[Claude 활용 24회 — AI에게 일을 위임하는 법] 6/24화: Claude Code 권한 설정 3단계 — 훅·샌드박스로 AI 사고 막는 법

By AICosmus
2026년 08월 06일 12 Min Read
2

3줄 요약

  1. Claude Code에는 권한 모드 · 샌드박스 · 훅이라는 3중 방어 체계가 있다.
  2. 훅(Hook)은 에이전트가 절대 우회할 수 없는 유일한 프로그래밍 가능 통제점이다.
  3. “하지 마”라고 프롬프트에 쓰는 건 통제가 아니다 — 코드로 막아야 막은 것이다.

지난 5화에서 CLAUDE.md로 ‘저장소의 헌법’을 세우는 법을 다뤘다. 금지사항을 적고, 컨벤션을 정하고, 빌드 명령을 명시하는 것. 훌륭한 출발점이다. 하지만 헌법만으로 사고가 안 나는 나라는 없다. 교통법규가 있어도 과속방지턱이 필요하고, 계약서가 있어도 에스크로가 필요하다. Claude Code 권한 설정도 마찬가지다. 프롬프트에 적은 규칙은 ‘권고’이고, 코드로 박은 방어선이 ‘통제’다. 이번 화에서는 그 통제의 세 겹을 전부 파헤친다.

스테이징 DB가 날아간 금요일 오후

금요일 오후 4시. 다음 주 배포를 앞두고, 테스트 데이터 정리를 Claude Code에 맡겼다. 4화에서 배운 대로 작업 지시서도 잘 썼고, 5화에서 다듬은 CLAUDE.md에는 “프로덕션·스테이징 DB에 직접 접속하지 않는다”는 조항까지 들어가 있었다.

“로컬 테스트 DB에서 orders 테이블의 user_id가 NULL인 orphaned 레코드를 정리해줘.”

Claude Code는 충실하게 움직였다. 프로젝트 디렉터리를 탐색하고, .env 파일을 읽어 DB 접속 정보를 찾았다. 문제는 그 파일이 .env.local이 아니라 .env.staging이었다는 것. 로컬 개발 DB가 아니라 QA 팀 전원이 쓰고 있던 스테이징 환경이었다.

Claude Code는 프로답게 먼저 SELECT를 돌려 결과를 보여줬다. “이 347건을 삭제하겠습니다. 진행할까요?” 나는 ‘승인’을 눌렀다. 한 주 내내 같이 작업하며 100번 넘게 눌러온 그 버튼을. 숫자가 좀 많다 싶었지만, 테스트 DB니까 그럴 수 있지.

3초 뒤 슬랙 알림이 울렸다.

“혹시 지금 스테이징 DB 건드리고 있어요?”

복구에 3시간이 걸렸다. 매일 백업이 있어서 데이터는 살았지만, QA 팀은 오후 작업을 전부 다시 해야 했다. 맥주 두 박스로 무마할 수 있는 수준이었다는 게 불행 중 다행이었다.

집에 가는 지하철에서 자문했다. 나는 왜 ‘승인’을 눌렀는가?

답은 단순하다. 습관이었다. 보안 업계에서 말하는 경고 피로(alert fatigue). 매번 묻는 권한 요청에 반복적으로 “Yes”를 누르다 보면, 정작 위험한 요청도 그냥 통과시킨다. 그리고 CLAUDE.md에 “스테이징 DB에 접속하지 마”라고 써놨다 해도, Claude Code가 .env.staging을 .env.local로 착각하는 걸 막지는 못한다. 에이전트는 ‘의도적 위반’을 한 게 아니라, 맥락을 잘못 파악한 것이니까.

그날 이후 세 가지를 바꿨다. 그 세 가지가 오늘 이야기하려는 3중 방어다.

Claude Code 권한 설정 — 3중 방어의 구조를 이해하라

AI 에이전트에게 일을 맡기려면, 위임과 통제가 동시에 작동해야 한다. 이 시리즈가 계속 말해온 것처럼 — AI가 코드를 짜주는 시대가 아니라 AI에게 일을 위임하는 시대다. 그런데 위임의 전제는 통제다. 아무 통제 없이 “알아서 해”라고 하면, 그건 위임이 아니라 방치다.

Claude Code는 이 문제를 세 겹의 방어선으로 풀 수 있게 설계되어 있다.

Claude Code 권한 설정 3중 방어 구조 다이어그램

1층 — 권한 모드(Permission Modes): 자동 승인의 범위를 정한다

Claude Code를 실행하면, 기본적으로 모든 도구 사용에 대해 사용자 승인을 요청한다. 파일을 읽을 때, 쓸 때, Bash 명령을 실행할 때, 매번 “이걸 해도 될까요?”라고 물어본다. 이 동작을 조절하는 것이 권한 모드다.

모드 자동 허용 범위 사용자 승인 필요 위험도
Ask (기본) 읽기 전용 도구 파일 쓰기, Bash, 네트워크 접근 🟢 낮음
Auto-edit 파일 읽기 + 쓰기 Bash 실행, 네트워크 접근 🟡 중간
Plan 모드 읽기 + 검색만 모든 쓰기/실행 🟢 매우 낮음
Full auto 전부 없음 🔴 높음

여기서 결정적인 포인트 하나. 3화에서 이미 경고했지만, Full auto를 켜려면 --dangerously-skip-permissions라는 플래그가 필요하다. 이름부터가 경고다. 이 플래그는 ‘내가 모든 책임을 진다’는 선언이다. 개인 실험 환경이 아니면 쓸 일이 없다.

대부분의 일상 작업에서는 Ask 모드에 세밀한 허용 목록(allowlist)을 더하는 것이 최선이다. 예를 들어 파일 읽기와 검색은 자동 허용하되, Bash 실행은 항상 물어보게 하는 식이다. 설정 파일(.claude/settings.json)에서 도구별 허용/차단 규칙을 지정할 수 있다.

{
  "permissions": {
    "allow": [
      "Read",
      "Glob",
      "Grep"
    ],
    "deny": [
      "WebFetch"
    ]
  }
}

이렇게 하면 Read, Glob, Grep은 매번 묻지 않고 바로 실행되고, WebFetch(외부 URL 가져오기)는 아예 차단된다. Bash는 목록에 없으므로 기본 동작(물어보기)을 따른다.

그런데 이것만으로는 내 스테이징 DB 사고를 막을 수 없다. Bash 실행을 물어보긴 했고, 나는 승인했으니까. 1층은 “무엇을 할 수 있는가”를 정하는 거지, “어떻게 하는 것이 위험한가”를 판단하지는 못한다.

2층 — OS 레벨 샌드박스: 울타리 자체를 물리적으로 세운다

1층이 ‘문 앞의 경비원’이라면, 2층은 ‘건물 자체의 벽’이다. 에이전트가 어떤 명령을 실행하든, 물리적으로 접근할 수 없는 영역을 만드는 것이다.

파일시스템 격리: Claude Code를 Docker 컨테이너 안에서 실행하면, 프로젝트 디렉터리만 마운트하고 호스트의 나머지 파일시스템은 보이지 않게 할 수 있다. .env.staging이 컨테이너 안에 없으면, 읽을 수도 없다.

# 프로젝트 디렉터리만 마운트하고, 환경 파일은 로컬 개발용만 복사
docker run -it --rm \
  -v "$(pwd)/src:/workspace/src" \
  -v "$(pwd)/.env.local:/workspace/.env:ro" \
  -w /workspace \
  my-claude-sandbox

네트워크 격리: 더 강력한 방어선은 네트워크를 끊는 것이다. 컨테이너에 --network=none을 걸면, 에이전트가 어떤 명령을 실행하든 외부 서버에 접속할 수 없다. 프로덕션 DB든 스테이징 DB든, 네트워크 자체가 없으니 물리적으로 불가능하다.

# 네트워크 완전 차단 — 에이전트가 외부에 연결할 수 없음
docker run -it --rm \
  --network=none \
  -v "$(pwd):/workspace" \
  -w /workspace \
  my-claude-sandbox

Windows 환경에서는 Windows Sandbox나 Hyper-V 격리 컨테이너를 활용할 수도 있다. WSL2 위에서 Docker를 돌리는 것이 가장 현실적인 조합이다.

샌드박스 방식 파일시스템 격리 네트워크 격리 설정 난이도 비고
Docker 컨테이너 ✅ ✅ 중 Windows는 WSL2+Docker Desktop
macOS sandbox-exec ✅ ✅ 상 Apple 비공식 API, 변경 가능성
Linux namespace ✅ ✅ 상 세밀한 제어 가능, 직접 구성 필요
Windows Sandbox ✅ ✅ 중 Pro/Enterprise 에디션 필요

샌드박스는 강력하지만 불편하다. 매번 컨테이너를 올리고, 파일을 마운트하고, 필요한 도구를 설치해야 한다. 일상적인 코딩 작업에서 매번 Docker 컨테이너를 띄우는 건 현실적이지 않을 수 있다. 그래서 3층이 필요하다.

3층 — 훅(Hooks): 에이전트가 우회할 수 없는 유일한 코드 레벨 통제점

여기가 오늘의 핵심이다.

훅은 Claude Code가 도구를 사용하기 직전(PreToolUse) 또는 직후(PostToolUse)에 실행되는 외부 프로그램이다. PreToolUse 훅은 도구 사용을 허용하거나 차단할 수 있다. 핵심적인 차이점이 하나 있다:

  • CLAUDE.md의 금지사항 → 에이전트가 최선을 다해 따르지만, 맥락 해석 오류로 위반할 수 있다
  • 권한 모드의 승인 프롬프트 → 인간이 ‘승인’을 습관적으로 누르면 무력화된다
  • 훅 → 에이전트가 도구를 호출하는 순간, 외부 프로그램이 가로채서 검사한다. 에이전트도, 사용자도 우회할 수 없다. 훅이 exit 2를 반환하면, 그 도구 호출은 물리적으로 실행되지 않는다.

CLAUDE.md가 ‘헌법’이라면, 훅은 ‘무장한 헌법재판소’다. 위헌 행위를 판단하고 즉시 집행한다.

이 구분이 왜 중요한지는 19화에서 프롬프트 인젝션을 다룰 때 다시 돌아온다. 외부에서 주입된 악의적 텍스트가 에이전트의 행동을 바꾸려 해도, 훅은 그 행동의 결과를 검사하기 때문에 프롬프트 레벨의 조작과는 독립적으로 작동한다. 프롬프트로 ‘하지 마’는 통제가 아니다. 코드로 막아야 막은 것이다.

이론은 여기까지다. 지금부터 실제로 돌아가는 코드를 만든다.

실전 훅 코드 4종 — 복붙해서 오늘부터 쓴다

0단계 — 훅 설정 파일 구조

Claude Code의 훅 설정은 JSON 파일에 들어간다. 세 곳에 둘 수 있다:

파일 위치 적용 범위 Git 추적
~/.claude/settings.json 전역 (모든 프로젝트) —
.claude/settings.json 현재 프로젝트 ✅ (팀 공유)
.claude/settings.local.json 현재 프로젝트 (개인) ❌ (.gitignore)

개인 안전망은 전역 설정에, 팀 표준은 프로젝트 설정에 넣는다. 기본 구조는 이렇다:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/guard-destructive.py",
            "timeout": 5000
          }
        ]
      }
    ]
  }
}

핵심 필드를 풀어보면:

  • matcher: 어떤 도구에 훅을 걸 것인가. "Bash", "Write", "Edit", "*"(전체) 등
  • type: "command"(로컬 스크립트 실행) 또는 "http"(외부 HTTP 엔드포인트 호출)
  • command: 실행할 프로그램. 표준 입력(stdin)으로 도구 호출 정보(JSON)를 받는다
  • timeout: 밀리초. 훅이 이 시간 안에 응답하지 않으면 도구 호출 차단

훅 프로그램은 표준 입력으로 이런 JSON을 받는다:

{
  "session_id": "abc123",
  "tool_name": "Bash",
  "tool_input": {
    "command": "psql -h staging.internal -c 'DELETE FROM orders WHERE user_id IS NULL'"
  }
}

그리고 종료 코드로 판결을 내린다:

  • exit 0: 허용 — 도구 호출 진행
  • exit 2: 차단 — 도구 호출 중단. 표준 에러(stderr)에 쓴 메시지가 Claude Code에 전달되어, 에이전트가 왜 차단되었는지 알 수 있다

이제 실전 코드로 가자. 아래 스크립트들은 모두 프로젝트 루트의 .claude/hooks/ 디렉터리에 둔다.

실습 1 — 파괴적 명령어 차단: rm -rf, DROP TABLE, force push

가장 먼저 막아야 할 것들. 재귀 삭제, 데이터베이스 파괴, Git 히스토리 조작.

#!/usr/bin/env python3
"""guard-destructive.py — 파괴적 명령어 차단 훅

위치: .claude/hooks/guard-destructive.py
용도: rm -rf, DROP TABLE, git push --force 등 되돌리기 어려운 명령 차단
"""
import json
import re
import sys

def main():
    data = json.load(sys.stdin)
    command = data.get("tool_input", {}).get("command", "")

    DANGEROUS_PATTERNS = [
        # 파일시스템 파괴
        (r"rm\s+-(r|rf|fr)\s", "재귀 삭제(rm -rf)"),
        (r"rmdir\s+/s", "디렉터리 삭제(rmdir /s, Windows)"),
        # 데이터베이스 파괴
        (r"DROP\s+(TABLE|DATABASE|SCHEMA)", "DROP 명령"),
        (r"TRUNCATE\s+TABLE", "TRUNCATE 명령"),
        (r"DELETE\s+FROM\s+\w+\s*;", "WHERE 없는 DELETE"),
        # Git 히스토리 조작
        (r"git\s+push\s+.*--force", "force push"),
        (r"git\s+reset\s+--hard", "hard reset"),
        # 시스템 파괴
        (r"mkfs\.", "파일시스템 포맷"),
        (r"dd\s+if=.*of=/dev/", "디스크 직접 쓰기"),
    ]

    for pattern, description in DANGEROUS_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            print(
                f"BLOCKED: {description}이(가) 감지되었습니다.\n"
                f"명령어: {command[:200]}\n"
                f"이 작업이 정말 필요하다면 훅을 일시 해제하고 수동으로 실행하세요.",
                file=sys.stderr,
            )
            sys.exit(2)

    sys.exit(0)

if __name__ == "__main__":
    main()

이 스크립트가 작동하면, Claude Code가 rm -rf ./old_data를 실행하려 할 때 이런 메시지를 보게 된다:

Hook blocked tool call: BLOCKED: 재귀 삭제(rm -rf)이(가) 감지되었습니다.
명령어: rm -rf ./old_data
이 작업이 정말 필요하다면 훅을 일시 해제하고 수동으로 실행하세요.

에이전트는 차단 이유를 읽고, 대안적인 접근 방법을 찾는다. 예를 들어 “파일을 하나씩 삭제하겠습니다” 또는 “삭제 대상 목록을 먼저 보여드리겠습니다”라고 전환한다. 이것이 프롬프트 지시와의 결정적인 차이다. 프롬프트는 무시할 수 있지만, 차단된 도구 호출은 물리적으로 실행이 안 되므로 우회가 불가능하다.

실습 2 — 프로덕션·스테이징 DB 접속 차단

내 금요일 오후 사고를 직접 막아줄 훅이다. 핵심 아이디어는 단순하다: 프로덕션과 스테이징 DB의 호스트명·IP 패턴을 등록해두고, 그 패턴이 명령어에 포함되면 차단한다.

#!/usr/bin/env python3
"""guard-prod-db.py — 프로덕션/스테이징 DB 접속 차단 훅

위치: .claude/hooks/guard-prod-db.py
용도: 개발 환경이 아닌 DB에 대한 접속 시도 차단
"""
import json
import re
import sys

# ── 여기만 자기 환경에 맞게 수정 ──────────────────────
BLOCKED_PATTERNS = [
    # 호스트명 패턴
    r"prod[\.\-]",          # prod.db.internal, prod-mysql 등
    r"staging[\.\-]",       # staging.db.internal 등
    r"live[\.\-]",          # live.db.company 등
    # IP 대역 (예: 프로덕션이 10.0.1.x 대역)
    r"10\.0\.1\.\d+",
    # RDS/Cloud SQL 엔드포인트 패턴
    r"\.rds\.amazonaws\.com",
    r"\.sql\.goog",
]

# 이 패턴에 매칭되면 허용 (로컬 개발 DB)
ALLOWED_PATTERNS = [
    r"localhost",
    r"127\.0\.0\.1",
    r"host\.docker\.internal",
    r"\.local\b",
]
# ───────────────────────────────────────────────────────

def main():
    data = json.load(sys.stdin)
    command = data.get("tool_input", {}).get("command", "")

    # DB 클라이언트 명령이 아니면 패스
    db_clients = ["psql", "mysql", "mongosh", "mongo", "redis-cli", "sqlcmd"]
    if not any(client in command for client in db_clients):
        # DB 클라이언트가 아니더라도, 환경 변수에 프로덕션 URL이 있을 수 있음
        if "DATABASE_URL" not in command and "DB_HOST" not in command:
            sys.exit(0)

    # 허용 패턴 체크 (로컬 DB는 통과)
    for pattern in ALLOWED_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            sys.exit(0)

    # 차단 패턴 체크
    for pattern in BLOCKED_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            print(
                f"BLOCKED: 프로덕션/스테이징 DB 접속이 감지되었습니다.\n"
                f"매칭 패턴: {pattern}\n"
                f"로컬 개발 DB(localhost, 127.0.0.1)만 사용할 수 있습니다.\n"
                f"명령어: {command[:200]}",
                file=sys.stderr,
            )
            sys.exit(2)

    sys.exit(0)

if __name__ == "__main__":
    main()

이 훅의 설계 포인트 세 가지:

  1. 화이트리스트 우선: localhost와 127.0.0.1은 항상 허용. 로컬 개발이 막히면 아무 작업도 못 한다.
  2. 패턴 분리: BLOCKED_PATTERNS와 ALLOWED_PATTERNS를 상단에 모아서, 환경이 바뀌면 그 부분만 고치면 된다.
  3. DB 클라이언트 필터: psql이나 mysql 같은 DB 클라이언트 명령이 아니면 대부분 패스시켜서, 일반적인 Bash 작업을 느리게 만들지 않는다.

실습 3 — 외부 데이터 전송 차단

세 번째 방어선은 데이터 유출(exfiltration) 방지다. 에이전트가 코드를 읽고 분석하는 건 좋지만, 그 내용을 외부 서버로 보내는 건 막아야 한다. 특히 규제가 강한 환경이나 감사 이력이 남아야 하는 조직에서는 필수다.

#!/usr/bin/env python3
"""guard-exfil.py — 외부 데이터 전송 차단 훅

위치: .claude/hooks/guard-exfil.py
용도: curl POST, wget upload, scp 등 외부 전송 시도 차단
"""
import json
import re
import sys

# 전송이 허용되는 목적지 (패키지 매니저, 사내 서비스 등)
SAFE_DESTINATIONS = [
    r"localhost",
    r"127\.0\.0\.1",
    r"::1",
    r"registry\.npmjs\.org",
    r"pypi\.org",
    r"github\.com",
    r"api\.github\.com",
]

# 데이터 전송을 나타내는 패턴
UPLOAD_PATTERNS = [
    # curl로 데이터 전송
    r"curl\s+.*(-d\s|--data\s|--data-binary\s|-F\s|--form\s|-T\s|--upload-file\s|-X\s*(POST|PUT|PATCH))",
    # wget으로 POST
    r"wget\s+.*--post",
    # netcat으로 데이터 전송
    r"(nc|netcat)\s+",
    # scp, rsync 원격 전송
    r"scp\s+.*:",
    r"rsync\s+.*:",
    # Python/Node로 HTTP 요청
    r"python.*requests\.(post|put|patch)",
    r"node.*fetch\(.*method.*POST",
]

def main():
    data = json.load(sys.stdin)
    command = data.get("tool_input", {}).get("command", "")

    # 전송 패턴이 아니면 바로 허용
    is_upload = False
    matched_pattern = ""
    for pattern in UPLOAD_PATTERNS:
        if re.search(pattern, command, re.IGNORECASE):
            is_upload = True
            matched_pattern = pattern
            break

    if not is_upload:
        sys.exit(0)

    # 전송 대상이 안전한 목적지인지 확인
    for safe in SAFE_DESTINATIONS:
        if re.search(safe, command, re.IGNORECASE):
            sys.exit(0)

    # 안전하지 않은 외부 전송 차단
    print(
        f"BLOCKED: 외부 데이터 전송이 감지되었습니다.\n"
        f"매칭 패턴: {matched_pattern}\n"
        f"허용된 목적지: localhost, npmjs.org, pypi.org, github.com\n"
        f"다른 외부 서비스로의 데이터 전송은 수동으로 실행하세요.\n"
        f"명령어: {command[:200]}",
        file=sys.stderr,
    )
    sys.exit(2)

if __name__ == "__main__":
    main()

이 훅은 OWASP Top 10에서도 강조하는 ‘최소 권한 원칙’의 구현이다. 에이전트가 데이터를 읽는 것과 보내는 것은 완전히 다른 위험 수준이며, 이 훅은 그 경계를 코드로 강제한다.

보너스 — HTTP 훅으로 감사 시스템 연동

규모가 있는 팀이라면, “누가 언제 무슨 도구를 실행했는가”를 중앙에서 추적하고 싶을 것이다. HTTP 훅은 이를 위해 존재한다. 모든 도구 호출을 외부 감사(audit) 서버에 기록하는 구성이다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "http",
            "url": "http://audit.internal:9090/v1/hook/pre-tool",
            "timeout": 3000
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "http",
            "url": "http://audit.internal:9090/v1/hook/post-tool",
            "timeout": 3000
          }
        ]
      }
    ]
  }
}

감사 서버 쪽은 간단한 FastAPI 앱이면 충분하다:

"""audit_server.py — 훅 감사 로그 수신 서버 (최소 구현)"""
from datetime import datetime, timezone
from fastapi import FastAPI, Request
import json
from pathlib import Path

app = FastAPI()
LOG_DIR = Path("./audit_logs")
LOG_DIR.mkdir(exist_ok=True)

@app.post("/v1/hook/pre-tool")
async def pre_tool_hook(request: Request):
    body = await request.json()
    log_entry = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "phase": "pre",
        "tool_name": body.get("tool_name"),
        "tool_input_preview": str(body.get("tool_input", ""))[:500],
        "session_id": body.get("session_id"),
    }
    log_file = LOG_DIR / f"{datetime.now(timezone.utc).strftime('%Y-%m-%d')}.jsonl"
    with open(log_file, "a", encoding="utf-8") as f:
        f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
    # exit 0 역할: 200 응답 = 허용
    return {"status": "logged"}

@app.post("/v1/hook/post-tool")
async def post_tool_hook(request: Request):
    body = await request.json()
    log_entry = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "phase": "post",
        "tool_name": body.get("tool_name"),
        "session_id": body.get("session_id"),
    }
    log_file = LOG_DIR / f"{datetime.now(timezone.utc).strftime('%Y-%m-%d')}.jsonl"
    with open(log_file, "a", encoding="utf-8") as f:
        f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
    return {"status": "logged"}

HTTP 훅의 응답 규칙은 간단하다: 2xx 응답 = 허용, 4xx 응답 = 차단(차단 시 응답 본문이 에이전트에 전달된다). 타임아웃되면 도구 호출이 차단되므로, timeout을 너무 짧게 잡으면 네트워크 지연 때문에 정상 작업이 멈출 수 있다.

Claude Code PreToolUse 훅 실행 흐름도

전체 조립 — settings.json 완성본

지금까지 만든 4개의 훅을 하나의 설정 파일에 합치면 이렇다. 이대로 .claude/settings.json에 넣으면 된다:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/guard-destructive.py",
            "timeout": 5000
          },
          {
            "type": "command",
            "command": "python .claude/hooks/guard-prod-db.py",
            "timeout": 5000
          },
          {
            "type": "command",
            "command": "python .claude/hooks/guard-exfil.py",
            "timeout": 5000
          }
        ]
      }
    ]
  }
}

같은 도구(Bash)에 여러 훅을 걸면, 전부 순서대로 실행된다. 하나라도 exit 2를 반환하면 도구 호출이 차단된다. 즉, 세 개의 훅이 AND 관계로 작동한다 — 전부 통과해야만 실행된다.

Windows에서 python 명령이 안 잡히면, py나 Python의 전체 경로를 지정한다. Git Bash가 설치되어 있다면 bash .claude/hooks/guard.sh 형태도 가능하다.

함정 — 이 회차에서 반드시 피해야 할 것 3가지

함정 1: “하지 마”는 통제가 아니다

이 시리즈에서 가장 많이 반복할 문장이다. CLAUDE.md에 “프로덕션 DB에 접속하지 말 것”이라고 쓰면, Claude Code는 그 지시를 읽고 따르려고 노력한다. 하지만 에이전트가 규칙을 어기는 대부분의 경우는 ‘의도적 위반’이 아니라 ‘맥락 판단 오류’다.

에이전트가 .env.staging을 읽으면서 “이것은 로컬 개발 환경의 설정이다”라고 잘못 판단하면, 본인 기준으로는 규칙을 지킨 것이다. 프롬프트 기반 지시는 에이전트의 판단력에 의존하지만, 훅 기반 차단은 판단과 무관하게 패턴을 검사한다. staging이라는 문자열이 명령어에 있으면, 에이전트가 뭐라고 생각하든 차단된다.

CLAUDE.md는 가이드라인, 훅은 법 집행이다. 둘 다 필요하지만, 위험한 행동에는 반드시 훅이 있어야 한다.

함정 2: –dangerously-skip-permissions의 달콤한 유혹

3화에서 “첫날 쓰지 말 것”이라고 경고했던 그 플래그다. 매번 ‘승인’을 누르는 게 귀찮아지면, 이 플래그로 모든 승인을 건너뛰고 싶은 유혹이 온다. 특히 반복 작업을 자동화할 때.

문제는 이 플래그가 1층(권한 모드)의 방어선을 완전히 해체한다는 것이다. 다행히 훅(3층)은 이 플래그와 독립적으로 작동한다. --dangerously-skip-permissions를 켜더라도 PreToolUse 훅은 여전히 실행된다. 하지만 플래그를 켤 만큼 귀찮다면, 차라리 도구별 허용 목록(allowlist)을 세밀하게 설정하는 게 정답이다.

Claude Code의 공식 문서에서도 이 플래그는 CI/CD 파이프라인이나 완전 격리된 샌드박스 환경에서만 사용하라고 권고한다.

함정 3: 훅을 만들고 테스트하지 않는 것

훅 스크립트도 코드다. 코드에는 버그가 있다. 정규식이 너무 느슨하면 정상 명령까지 차단하고, 너무 좁으면 위험한 명령이 빠져나간다.

반드시 훅을 테스트하라. 가장 간단한 방법은 직접 JSON을 파이프로 넣어보는 것이다:

# 차단되어야 하는 케이스 — exit code가 2여야 정상
echo '{"tool_input":{"command":"rm -rf /var/data"}}' | python .claude/hooks/guard-destructive.py
echo "Exit code: $?"

# 허용되어야 하는 케이스 — exit code가 0이어야 정상
echo '{"tool_input":{"command":"ls -la"}}' | python .claude/hooks/guard-destructive.py
echo "Exit code: $?"

# 스테이징 DB 차단 테스트
echo '{"tool_input":{"command":"psql -h staging.db.internal"}}' | python .claude/hooks/guard-prod-db.py
echo "Exit code: $?"

# 로컬 DB는 허용 테스트
echo '{"tool_input":{"command":"psql -h localhost"}}' | python .claude/hooks/guard-prod-db.py
echo "Exit code: $?"

더 견고하게 하려면 pytest로 훅 스크립트의 단위 테스트를 작성한다. 훅도 소프트웨어고, 소프트웨어에는 테스트가 필요하다.

그리고 하나 더: 훅의 오탐(false positive)은 생산성을 죽인다. 정상적인 rm file.txt(재귀가 아닌 단일 파일 삭제)까지 차단하면, 사용자와 에이전트 모두 좌절한다. 위에서 패턴을 rm\s+-(r|rf|fr)\s로 쓴 이유가 바로 이것이다 — -r 또는 -rf 플래그가 있을 때만 잡고, 단순 rm은 통과시킨다.

이번 회차의 수익화 지점

오늘 만든 3종 훅 세트를 보자. guard-destructive.py, guard-prod-db.py, guard-exfil.py — 이것은 팀 보안 표준이 될 수 있는 자산이다.

여러 팀에서 Claude Code를 도입하기 시작하면(20화에서 다루겠지만, 도입은 동료 관계망을 타고 퍼진다), 가장 먼저 부딪히는 질문이 “안전하게 쓰려면 뭘 설정해야 해?”다. 이 질문에 대한 답을 복붙 가능한 설정 세트 + 30분 온보딩 세션으로 패키징하면, 사내 교육 자료 또는 외부 워크숍 콘텐츠가 된다.

특히 규제 산업(금융, 의료, 공공)에서 AI 코딩 도구를 도입할 때, “에이전트의 행동을 어떻게 통제하는가?”는 감사 체크리스트의 필수 항목이다. 오늘의 HTTP 감사 훅처럼 행위 추적이 가능한 구성을 갖추고 있다는 것 자체가 도입 승인의 허들을 낮춘다. 이런 구성을 대신 세팅해주는 컨설팅은 시간당으로 팔 수 있는 서비스다.

12화에서 다룰 ‘플러그인 패키징’까지 가면, 오늘의 훅 스크립트들을 하나의 보안 플러그인으로 묶어 배포하는 것도 가능하다. 설치 한 번이면 팀 전체에 동일한 방어선이 깔린다.


다음 7화 예고: 에이전트와 오래 일하다 보면 ‘대화 기억’이 흐려지는 순간이 온다. 컨텍스트 윈도우가 가득 차면 무엇이 버려지고, /compact를 언제 써야 하며, 실패한 시도가 쌓여 같은 실수를 반복하는 ‘컨텍스트 오염’은 어떻게 끊는가. 리셋이 정답인 순간을 구별하는 법을 다룬다.

시리즈 안내
이 글은 Claude 활용 24회 — AI에게 일을 위임하는 법 시리즈의 6화입니다.
이전: 5화 — CLAUDE.md 작성법 5가지: 저장소의 헌법을 쓰는 실전 가이드
다음: 7화 — 컨텍스트 관리: /compact·세션 분할·오염 리셋 타이밍
관련: 3화 — Claude Code 설치 가이드: 30분 만에 첫 성공까지 | 4화 — AI 작업 지시서 4요소


📚 시리즈: Claude 활용 24회 — AI에게 일을 위임하는 법 (총 24화 중 6화)
◀ 이전 5화  (다음 차수는 아직 게시되지 않았습니다)

자주 묻는 질문

Claude Code 권한 모드 종류는 어떤 게 있고 어떤 걸 써야 안전한가요?

Claude Code에는 Ask(기본), Auto-edit, Plan, Full auto 네 가지 권한 모드가 있습니다. Ask 모드는 읽기 전용 도구만 자동 허용하고 파일 쓰기나 Bash 실행은 매번 승인을 요청하므로 가장 안전하며, Full auto는 모든 동작을 자동 승인하므로 위험도가 가장 높습니다.

CLAUDE.md에 금지사항을 적어두면 Claude Code가 반드시 지키나요?

CLAUDE.md에 적은 규칙은 프롬프트 수준의 권고이므로 Claude Code가 맥락을 잘못 파악하면 의도치 않게 위반할 수 있습니다. 프롬프트에 쓰는 것은 통제가 아니라 권고이며, 실제 통제는 훅(Hook)이나 샌드박스처럼 코드로 강제하는 방어선이어야 막을 수 있습니다.

Claude Code 훅(Hook)은 다른 방어 수단과 뭐가 다른가요?

훅은 에이전트가 절대 우회할 수 없는 유일한 프로그래밍 가능 통제점입니다. 권한 모드나 CLAUDE.md 규칙은 에이전트의 판단에 의존하지만, 훅은 코드 레벨에서 특정 동작을 차단하므로 맥락 오해나 경고 피로로 인한 사고를 원천적으로 방지할 수 있습니다.


Tags:

AI 보안Claude CodeClaude 활용 24회 — AI에게 일을 위임하는 법-6화개발자 도구에이전트 권한연재:Claude 활용 24회 — AI에게 일을 위임하는 법훅 설정
작성자

AICosmus

Follow Me
다른 기사
opencode 모델 라우팅 에이전트 배치 개념
Previous

[opencode 시즌 2 심화 — 나만의 도메인 특화 에이전트 만들기] 5/12화: opencode 모델 라우팅 4단계 실전 가이드 2026

Podman 루트리스 컨테이너 개념 일러스트
Next

Podman 실전 입문 2026 — Docker 대체 루트리스 컨테이너

2 댓글
  1. Podman 실전 입문 2026 — Docker 대체 루트리스 컨테이너 - AICosmus 댓글:
    2026년 08월 06일, 11:10 오전

    […] […]

    답글
  2. [Claude 활용 24회 — AI에게 일을 위임하는 법] 7/24화: 컨텍스트 관리 5단계 — 100만 토큰 시대의 절약 기술 - AICosmus 댓글:
    2026년 08월 07일, 7:09 오전

    […] 시리즈: Claude 활용 24회 — AI에게 일을 위임하는 법 (총 24화 중 7화)◀ 이전 6화  (다음 차수는 아직 게시되지 […]

    답글

답글 남기기 응답 취소

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

최신 글

  • 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다
  • AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다
  • 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다
  • AI 트렌드 뉴스 4선 — 2026년 9월 둘째 주, 수학 난제부터 반도체 현장까지 AI가 증명을 시작했다
  • 디지털자산 뉴스 4선 — 2026년 9월 8일, $320M 해킹과 CBDC 실거래가 같은 주에 터지다

최신 댓글

  1. 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다의 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다 - AICosmus
  2. AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다의 디지털자산 뉴스 4선 — 2026년 9월 12일, 입법과 인프라가 동시에 다음 단계로 넘어가다 - AICosmus
  3. AI 트렌드 뉴스 4선 — 2026년 9월 둘째 주, 수학 난제부터 반도체 현장까지 AI가 증명을 시작했다의 AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다 - AICosmus
  4. 디지털자산 뉴스 4선 — 2026년 9월 8일, $320M 해킹과 CBDC 실거래가 같은 주에 터지다의 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다 - AICosmus
  5. 디지털자산 뉴스 4선 — 2026년 9월 10일, 은행과 빅테크가 같은 날 스테이블코인 인프라를 가동하다의 AI 트렌드 뉴스 5선 — 2026년 9월 11일, 자본과 규제가 같은 속도로 달린다 - AICosmus
  • About
  • Contact
  • Disclaimer
  • Privacy - Policy
  • Terms of Service
Copyright 2026 — AICosmus. All rights reserved. Blogsy WordPress Theme