지식위키

Claude Code · 가드레일: 권한·승인·샌드박스

Claude Code · 가드레일: 권한·승인·샌드박스

한 줄 요약

가드레일은 “AI가 무엇을 할 수 있고 못 하는가”를 모델의 양심이 아니라 프로그램(하네스)이 강제로 막는 3중 안전장치다. — 왜 배우나: 프롬프트 인젝션으로 AI가 속아 넘어가도, 회사가 정한 선을 OS와 규칙이 물리적으로 지켜주기 때문이다.

그림

flowchart TD
  A[AI가 도구를 쓰려고 시도] --> B{"도구가 아예 빠져 있나?<br/>맨이름 deny"}
  B -- 그렇다 --> Z[AI는 그 도구의 존재조차 모름]
  B -- 아니다 --> C["1차 관문: PreToolUse 훅 실행"]
  C -- 훅이 즉시 거부 exit 2 --> X[차단]
  C -- 통과 --> D{"2차 관문: 권한 규칙<br/>deny → ask → allow 순"}
  D -- deny에 걸림 --> X
  D -- ask에 걸림 --> P[사람에게 물어봄]
  D -- allow에 걸림 --> S{"3차 관문: 샌드박스 대상?"}
  D -- 아무것도 안 걸림 --> M{권한 모드 강도}
  M -- 자동/우회 --> S
  M -- 기본/계획 --> P
  S -- 샌드박스에 갇혀 안전 --> R[묻지 않고 실행]
  S -- 아니면 --> P

[!note] 3중 관문의 핵심 AI는 차례로 훅 → 권한 규칙 → 샌드박스를 통과해야 실제로 손을 댈 수 있다. 셋 중 하나라도 막으면 끝이다. 그리고 이 모든 선은 CLAUDE.md나 프롬프트로는 절대 못 바꾸고, 회사 관리자가 내려보낸 managed 설정이 맨 위에서 군림한다.

쉽게 풀기

AI 비서를 “능력 좋지만 가끔 속는 신입 직원”이라고 생각하자. 가드레일은 이 직원에게 “착하게 굴어라”라고 부탁하는 게 아니라, 할 수 없게 회사 시스템 자체를 잠가두는 것이다. 잠금장치가 3겹이다.

1겹 — 권한 규칙(allow / ask / deny): 출입증 명단 도구 하나, 명령어 한 줄, 파일 한 개, 인터넷 주소 하나까지 명단으로 통제한다.

  • allow = “이건 물어보지 말고 그냥 해” (무사통과)
  • ask = “이건 할 때마다 나한테 확인받아”
  • deny = “이건 절대 금지”

명단을 읽는 순서가 중요하다. deny 먼저, 그다음 ask, 마지막 allow. 위에서 처음 걸리는 줄이 결론이다. 그래서 deny에 걸리면 아무리 자세한 allow가 있어도 무조건 차단이다. (보안에서 “막는 쪽이 항상 이긴다”는 원칙)

2겹 — 권한 모드: 질문 빈도 다이얼 “얼마나 자주 사람에게 물어볼지”의 기본 강도다. 자동차의 주행 모드(에코/스포츠)처럼 전체 분위기를 정한다.

모드한마디로
default / plan읽기만 자유, 나머지는 다 물어봄 (가장 신중)
acceptEdits작업 폴더 안 편집·파일정리(mkdir/rm 등)는 자동
auto거의 다 자동, 대신 백그라운드 분류기 AI가 위험 검사
dontAskallow 명단과 읽기전용만 실행, 나머진 그냥 거부
bypassPermissions거의 다 통과 (가장 위험, 회사가 잠글 수 있음)

3겹 — 샌드박스: 방탄 작업실 앞의 두 겹이 “명단으로 막기”라면 샌드박스는 OS 차원에서 물리적으로 가두기다. Bash 명령이 손댈 수 있는 파일과 접속할 수 있는 인터넷 주소를 운영체제가 강제로 제한한다. 비유하면, 신입에게 일을 시키되 창문도 전화선도 없는 격리된 방에서만 일하게 하는 것이다. 설령 프롬프트 인젝션으로 AI가 “회사 비밀을 외부로 보내!”라는 악성 지시에 속아도, 그 방에는 외부로 나가는 선 자체가 없어 실패한다.

관문 사이의 합의 규칙(꼭 기억)

  • 1겹 훅이 “거부(exit 2)“하면 → allow 명단이 있어도 진다 (훅이 먼저 칼을 휘두름).
  • 반대로 훅이 “허용”이라 해도 → deny 명단은 못 이긴다 (deny는 최강).
  • 샌드박스에 안전하게 갇힌 bash는 → 기본적으로 묻지 않고 실행한다(autoAllowBashIfSandboxed가 켜져 있으면). 방 자체가 안전하니 매번 확인할 필요가 없다는 논리.

핵심 정리

관문무엇을 막나누가 강제하나
권한 규칙도구·명령·파일·도메인Claude Code (모델 아님)
권한 모드질문 빈도의 기본값Claude Code
샌드박스파일/네트워크 접근OS 커널

[!note] permissions 블록 주요 필드

  • allow / ask / deny — 규칙 배열. 형식은 Tool 또는 Tool(상세지정자)
  • defaultMode — 위 6가지 모드 중 기본값 (v2.1.142+부터 프로젝트·local 설정의 auto는 무시됨)
  • disableBypassPermissionsMode: "disable" — bypass 모드와 --dangerously-skip-permissions 자체를 봉인
  • disableAutoMode: "disable" — auto 모드 봉인
  • additionalDirectories — 작업 폴더 추가(파일 접근만, 설정은 안 읽음)

[!note] 규칙 지정자(specifier) 문법 — 자주 쓰는 것

  • Bash / Bash(*) — 모든 Bash. deny면 도구를 통째로 제거
  • Bash(npm run *)npm run으로 시작 (공백=단어 경계 강제)
  • Read(./.env) / Read(**/.env) — gitignore식 매칭. 앵커: //절대, ~/홈, /프로젝트루트, ./현재폴더
  • WebFetch(domain:*.example.com) — 호스트 매칭. *.는 서브도메인만(본도메인 제외)
  • mcp__puppeteer__* — 해당 MCP 서버 전체 도구
  • Agent(Explore) — 특정 서브에이전트 통제 / Cd(~/code/**)/cd 이동 대상 통제

[!note] sandbox 블록 주요 필드

  • enabled — 샌드박스 켜기 / failIfUnavailable: true — 샌드박스 못 켜면 아예 종료(엔터프라이즈 하드 게이트)
  • autoAllowBashIfSandboxed(기본 true) — 갇힌 bash 자동 승인 / allowUnsandboxedCommands(기본 true) — false면 탈출구(dangerouslyDisableSandbox) 완전 봉인
  • excludedCommands — 샌드박스 밖에서 돌릴 명령(예: docker *)
  • filesystem.allowWrite / filesystem.denyRead — 쓰기 허용/읽기 차단 경로
  • network.allowedDomains / network.deniedDomains — 아웃바운드 허용/차단 도메인(둘 다 걸리면 차단 우선)

[!note] 회사(managed)만 쓸 수 있는 최상위 잠금

  • allowManagedPermissionRulesOnly: true — 개발자가 자기 settings에 allow를 넣어도 무시, managed 규칙만 적용
  • allowManagedHooksOnly: true — managed/SDK/강제 플러그인 훅만 로드
  • allowManagedMcpServersOnly — managed가 허락한 MCP 서버만 인정
  • strictPluginOnlyCustomization — skills/agents/hooks/mcp를 플러그인·managed로만 제한
  • forceRemoteSettingsRefresh — 원격 설정을 받아오기 전엔 시작 차단(fail-closed)

실제 예시

회사 강제 + 샌드박스를 함께 거는 복붙용 최소 설정.

// .claude/settings.json (또는 managed-settings.json)
{
  "permissions": {
    "defaultMode": "default",
    "disableBypassPermissionsMode": "disable",
    "allow": [
      "Bash(npm run *)",
      "Bash(git commit *)",
      "WebFetch(domain:github.com)"
    ],
    "ask": [
      "Bash(git push *)"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Read(./.env)",
      "Read(**/secrets/**)",
      "WebSearch"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "allowUnsandboxedCommands": false,
    "excludedCommands": ["docker *"],
    "filesystem": {
      "allowWrite": ["/tmp/build", "~/.kube"],
      "denyRead": ["~/.aws/credentials"]
    },
    "network": {
      "allowedDomains": ["github.com", "*.npmjs.org"],
      "deniedDomains": ["uploads.github.com"],
      "allowLocalBinding": true
    }
  }
}

가장 빡빡한 “strict 프로필” 실제 소스 — 모든 bash를 묻고, 웹 도구는 통째로 제거한다.

// /home/seunghyeong/harness-work/claude-code/examples/settings/settings-strict.json
{
  "permissions": {
    "disableBypassPermissionsMode": "disable",
    "ask": [
      "Bash"
    ],
    "deny": [
      "WebSearch",
      "WebFetch"
    ]
  },
  "allowManagedPermissionRulesOnly": true,
  "strictKnownMarketplaces": [],
  "sandbox": {
    "autoAllowBashIfSandboxed": false,
    "excludedCommands": [],
    "network": {
      "allowUnixSockets": [],
      "allowAllUnixSockets": false,
      "allowLocalBinding": false,
      "allowedDomains": [],
      "httpProxyPort": null,
      "socksProxyPort": null
    },
    "enableWeakerNestedSandbox": false
  }
}

이 프로필의 의미: 맨이름 Bash를 ask로 두고 autoAllowBashIfSandboxed:false모든 bash가 매번 프롬프트된다. WebSearch·WebFetch는 deny라 도구 자체가 AI 눈앞에서 사라진다. allowManagedPermissionRulesOnly:true라 개발자가 allow를 추가해도 소용없다.

MDM으로 내려보내는 최소한의 회사 강제 예 — 단 한 줄로 “우회 모드 봉인”만 걸어도 강력하다.

// /home/seunghyeong/harness-work/claude-code/examples/mdm/managed-settings.json
{
  "permissions": {
    "disableBypassPermissionsMode": "disable"
  }
}

[!warning] 설계 체크리스트

  • deny 먼저 설계: 비밀 파일(Read(**/.env))·파괴 명령(Bash(rm -rf *))부터 deny에.
  • 맨이름 deny: ["Bash"](도구 제거) vs 스코프 Bash(rm *)(도구 유지+해당 호출만 차단) 의도 구분.
  • 앵커 혼동 주의: /Users/...는 절대경로가 아니라 프로젝트 루트 상대다. 절대경로는 //.
  • Bash(safe *)safe && other를 허용하지 않음(복합명령은 서브명령별 매칭). devbox run * 같은 러너는 내부 명령까지 열려 위험.
  • 모든 모드(bypass 제외)에서 보호 경로(.git/.claude/.bashrc 등) 쓰기는 자동 승인 안 됨 — allow로도 못 뚫음(안전 체크가 allow보다 먼저).
  • 엔터프라이즈 하드 게이트: failIfUnavailable:true + allowUnsandboxedCommands:false.

요약 & 셀프체크

  • 가드레일은 부탁이 아니라 강제다: 권한 규칙 → 권한 모드 → 샌드박스 3겹을 하네스와 OS가 직접 막는다.
  • 규칙은 deny → ask → allow 순으로 읽고 첫 매칭이 결론이다. deny는 어떤 범위에서도 못 뚫린다.
  • 회사 managed 설정이 최상위이며 CLAUDE.md·프롬프트로는 절대 못 바꾼다.

셀프체크:

  1. allow: ["Bash"]인데 Bash(rm -rf /)를 막고 싶다면 어떤 두 가지 방법이 있나? (deny 규칙 추가 / exit 2 PreToolUse 훅)
  2. deny: ["WebFetch"]deny: ["WebFetch(domain:evil.com)"]는 모델 입장에서 무엇이 다른가?
  3. 샌드박스가 켜져 있으면 bash 권한 프롬프트가 왜 사라질 수 있나? 그래도 프롬프트가 남는 경우는?

연결

CC_개요 · CC_30_hooks · CC_70_mcp-and-tools · _분석축_루브릭

[!tip] Codex 교차검증 (원분석 보존) 가드레일은 모델 프롬프트에 텍스트로 주입되는 게 아니라 도구 호출 게이트로 동작한다. 모델이 Bash/Edit를 호출하려는 순간 하네스가 가로채 규칙을 평가한다. 유일한 “주입형” 예외는 deny가 맨이름일 때로, 그 도구가 모델의 툴 카탈로그에서 통째 제거된다. PreToolUse 훅은 권한 프롬프트 전에 실행되며, 훅이 allow/ask를 반환해도 매칭되는 deny는 차단·ask는 여전히 프롬프트한다(deny-first 유지). 반대로 exit 2 훅은 권한 평가 전에 호출을 중단시켜 allow를 이긴다. 활용 패턴: allow:["Bash"]로 전부 무프롬프트로 돌리되 PreToolUse 훅으로 특정 명령만 거부. 권한 모드별 무프롬프트 범위: default/plan=읽기만, acceptEdits=작업디렉토리 편집+mkdir/touch/rm/mv/cp/sed, auto=전부(분류기 백그라운드 검사), dontAsk=allow·읽기전용 bash만, bypassPermissions=전부(명시적 ask와 rm -rf /·~ 서킷브레이커 예외). 샌드박스-권한 결합(defense-in-depth): 파일시스템 경계=sandbox.filesystem+Read/Edit deny 병합, 네트워크 경계=WebFetch(domain:)+allowedDomains/deniedDomains 병합. autoAllowBashIfSandboxed:true(기본)면 맨이름 Bash ask가 있어도 샌드박싱된 bash는 무프롬프트지만, Bash(git push *) 같은 내용 스코프 ask는 여전히 프롬프트.

근거 파일:

  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/permissions.md
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/permission-modes.md
  • /mnt/d/6study/10_프레임워크분석/_원문아카이브/claude-code/settings.md (193-205, 297-372, 393-411, 504-589행)
  • /home/seunghyeong/harness-work/claude-code/examples/settings/settings-strict.json
  • /home/seunghyeong/harness-work/claude-code/examples/settings/settings-bash-sandbox.json
  • /home/seunghyeong/harness-work/claude-code/examples/settings/settings-lax.json
  • /home/seunghyeong/harness-work/claude-code/examples/mdm/managed-settings.json