Claude Code 실무 가이드에서 권한 모드 순환과 bypassPermissions 경고를 봤다면, 이 문서는 그 권한을 설계하고, 에이전트의 행동을 강제하고, 조직 차원에서 고정하는 방법을 다룹니다. 개인 설정으로는 끌 수 없는 가드레일을 팀·조직 단위로 세우려는 개발자가 대상입니다. 에이전트 운영 원칙과 그 '왜'는 여기서 다시 정의하지 않습니다 — 에이전트 개념에 정본이 있습니다. 이 문서는 "어떻게 통제하고, 어떻게 강제하는가"만 봅니다. 이어지는 다섯 절은 권한 규칙 문법, 충돌 시 우선순위와 스코프, hooks로 라이프사이클 강제, managed settings로 조직 고정, 그리고 격리 워커·번들 순서로 갑니다.
권한 규칙 문법
권한 규칙은 "도구 + 범위"를 패턴으로 적어 무엇을 허용(allow)·질문(ask)·차단(deny)할지 정의합니다. 규칙은 세 가지 형태를 가집니다.
| 형태 | 의미 | 예 |
|---|---|---|
Tool | 도구 전체 | WebFetch |
Tool(specifier) | 도구 + 세부 범위 | Bash(npm run *) |
Tool(param:value) | 입력 파라미터별 (deny/ask만) | WebFetch(domain:example.com) |
도구별로 specifier 문법이 다릅니다. Bash는 와일드카드를 쓰되 경계가 단어 단위입니다 — Bash(npm run *)는 npm run으로 시작하는 명령을 매칭합니다( *는 word-boundary). Read·Edit는 gitignore 스타일 경로를 받습니다 — 프로젝트 상대 경로 Read(/src), 홈 기준 Read(~/notes), 절대 경로 Read(//etc/hosts). WebFetch는 도메인으로 WebFetch(domain:example.com)·WebFetch(domain:*.example.com). MCP 서버는 mcp__server(서버 전체)·mcp__server__*·mcp__server__tool(개별 도구). 서브에이전트는 Agent(Explore)·Agent(name)로 지정합니다.
규칙은 .claude/settings.json의 권한 블록에 deny·ask·allow로 나눠 적습니다. 아래는 저장소에서 반복되는 검증 작업은 통과시키고, 배포·게시·외부 전송·민감 파일 접근은 차단하거나 확인을 받는 예시입니다.
{
"permissions": {
"deny": ["Bash(rm -rf *)", "WebFetch", "Read(/secrets/**)", "mcp__github__*"],
"ask": ["Bash(git push *)", "Bash(npm publish *)"],
"allow": ["Bash(npm run *)", "Read(/src)", "Edit(/src)"]
}
}
현재 세션에서 어떤 규칙이 어느 설정 파일에서 왔는지는 Claude Code 안에서 /permissions로 출처까지 확인합니다. MCP 서버 설정·.env·.mcp.json에는 토큰·키가 섞이기 쉽습니다. 무엇을 입력해도 되는지(자사 소스코드 포함)는 권한 문법이 아니라 입력 정책의 문제이므로 안전에서 다룹니다. 다음은 이 규칙들이 충돌할 때 누가 이기는지입니다.
우선순위와 스코프 — 누구의 규칙이 이기는가
같은 동작에 여러 규칙이 걸리면 결과는 두 축으로 결정됩니다. 결론부터: 종류는 deny > ask > allow, 스코프는 강한 쪽이 약한 쪽을 덮습니다.
먼저 종류. deny 규칙은 스코프·구체성과 무관하게 이깁니다(deny-first) — 같은 동작에 더 구체적인 allow가 있어도 차단됩니다. "허용 목록에 있으니 되겠지"는 통하지 않습니다.
다음으로 스코프. 설정은 아래 우선순위(높음 → 낮음)로 병합됩니다.
| 스코프 | 위치 | 비고 |
|---|---|---|
| Managed settings | 서버/OS 정책·MDM | 최상단·override 불가 |
| 명령행 인자 | 세션 실행 시 전달 | 해당 세션 한정 |
| Local project | .claude/settings.local.json | 개인용(비버전) |
| Shared project | .claude/settings.json | 팀 공유(VCS 커밋) |
| User | ~/.claude/settings.json | 내 모든 저장소 |

두 축을 합치면 핵심은 하나입니다 — deny는 스코프를 가로질러 이깁니다. managed settings의 deny가 걸려 있으면 user 설정에서 같은 동작을 allow로 풀어도 차단됩니다. 그래서 팀 저장소의 .claude/settings.json에 deny로 박은 위험 명령은 개인 설정으로 되돌릴 수 없습니다. 어떤 규칙이 어느 스코프에서 적용 중인지는 세션에서 /permissions로 확인합니다. 다음 절의 hooks는 이 권한 규칙보다도 앞에서 동작할 수 있습니다.
hooks — 라이프사이클에 강제 장치 끼우기
hooks는 권한 규칙과 별개로, 특정 라이프사이클 시점에 스크립트를 강제로 실행해 동작을 검사하거나 차단하는 메커니즘입니다. 권한 규칙이 "무엇을 허용할지"를 정의한다면, hook은 "그 시점에 무조건 이 검사를 돌려라"를 강제합니다.
대표 이벤트로는 SessionStart, PreToolUse(도구 실행 전, 차단 가능), PostToolUse(실행 후), PermissionRequest(권한 prompt 시), Stop(턴 종료) 등 다수가 있습니다. 이벤트 종류는 버전마다 추가되므로 총수를 단정하지 않습니다 — 필요한 이벤트는 매번 레퍼런스에서 확인하세요. 각 hook은 matcher로 어떤 도구에 걸지 매칭합니다.
가장 흔한 형태인 command hook은 종료 코드로 결과를 전달합니다.
| 출력 | 의미 |
|---|---|
exit 0 | 통과 (결정 없음) |
exit 2 | 차단 — stderr 내용이 Claude에게 피드백으로 전달됨 |
| stdout JSON | decision으로 제어 (예: PermissionRequest hook이 "behavior":"allow"로 prompt 대신 자동 응답) |
단, PreToolUse에서 exit 0은 "승인"이 아니라 정상 권한 흐름이 그대로 이어진다는 뜻입니다. 강제력의 핵심은 순서입니다. PreToolUse hook은 권한 규칙보다 먼저 돌고, 도구 실행 전에 차단할 수 있습니다. hook의 deny는 allow 규칙도 덮습니다(단 hook의 allow가 deny 규칙을 덮지는 못합니다 — deny는 여전히 이깁니다). 여러 hook의 결정이 충돌하면 가장 제한적인 쪽이 우선합니다(deny > defer > ask > allow). 예를 들어 커밋 전 린트를 강제하려면, PreToolUse hook을 걸고 검사 스크립트를 돌린 뒤 실패 시 차단합니다.
npm run lint || exit 2
hook 설정은 권한과 같은 자리에 둡니다 — ~/.claude/settings.json(전역)·.claude/settings.json(프로젝트)·.claude/settings.local.json(비버전)·managed settings·plugin. 아래는 Bash 도구 호출에 사전 검사를 거는 예시입니다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/check-bash.sh"
}
]
}
]
}
}
권한 규칙과 hook의 정확한 선후·상호작용은 code.claude.com/docs/en/hooks 레퍼런스에서 재확인하세요. 다음은 이 규칙과 hook을 개인이 끌 수 없게 만드는 방법입니다.
조직 거버넌스 — managed settings로 고정
지금까지의 규칙과 hook은 팀 저장소에 커밋해 공유할 수 있지만, 개인이 자기 스코프에서 우회할 여지가 남습니다. 개인이 끌 수 없는 강제는 managed settings에서 옵니다. managed settings는 스코프 우선순위의 최상단이라 override되지 않으며, 여기에만 있는 전용 강제 필드로 무엇을 잠글지 정합니다.
| 필드 | 효과 |
|---|---|
allowManagedPermissionRulesOnly | managed 권한 규칙만 유효 — project/user 규칙은 적용 안 됨 |
disableBypassPermissionsMode | bypassPermissions 모드 자체를 차단 |
allowManagedHooksOnly | managed/plugin이 강제한 hook만 실행 |
allowManagedMcpServersOnly | managed allowlist에 있는 MCP 서버만 연결 |
strictPluginOnlyCustomization | skills·hooks 등의 커스터마이즈를 plugin/managed로만 제한 |
여기서도 deny-first가 스코프를 가로질러 적용됩니다 — managed deny와 user allow가 부딪치면 deny가 이깁니다. 이렇게 하면 "위험 명령 차단·bypass 금지·승인된 MCP 서버만"을 조직 정책으로 고정하고, 그 위에서 각 팀이 자기 규칙을 얹는 구조가 됩니다.
{
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"allowManagedMcpServersOnly": true,
"permissions": {
"disableBypassPermissionsMode": "disable",
"deny": [
"Read(/secrets/**)",
"Bash(curl *)"
],
"ask": [
"Bash(git push *)",
"Bash(npm publish *)"
],
"allow": [
"Bash(npm run *)"
]
}
}
실제로 어떤 managed 설정을 적용하는지·어떤 사내 MCP 서버와 plugin을 허용하는지는 조직 계정·정책에 따라 다릅니다.
다음은 권한을 좁힌 격리 워커와, 이 모든 표준을 묶어 배포하는 단위입니다.
격리와 번들 — 서브에이전트·plugins
가드레일의 마지막 두 조각은 권한을 좁힌 워커로 격리하기와 그 표준을 팀에 배포하기입니다.
서브에이전트는 격리된 컨텍스트·자체 system prompt·도구 allowlist(tools)·permissionMode를 가진 워커입니다. 정의는 .claude/agents/(프로젝트)·~/.claude/agents/(user)에 두며, YAML frontmatter(name·description·tools·model·permissionMode 등) + 마크다운 system prompt로 작성합니다. 가드레일 관점에서 핵심은 권한을 좁힌 워커로 격리한다는 점입니다 — 예를 들어 읽기 도구만 가진 탐색용 서브에이전트를 두면, 그 워커는 편집·셸을 애초에 못 합니다. 특정 서브에이전트는 Agent(name) 권한 규칙으로 deny할 수도 있습니다.
plugins는 skills·agents·hooks·MCP 서버를 묶어 마켓플레이스로 배포하는 번들입니다. 가드레일 관점에서는 팀에 표준(hook·권한 규칙·서브에이전트)을 배포하는 단위로 보면 됩니다 — 한 번 묶어 두면 팀원이 같은 강제 장치를 일괄로 받습니다.
한 가지 혼동 정리: Claude Code의 skills(.claude/skills/의 SKILL.md)는 비개발자용 Claude Skills와 같은 Agent Skills 표준의 개발자/CLI 표면입니다 — 별개 기능이 아니라 같은 표준의 다른 표면입니다.
그리고 bypassPermissions·--dangerously-skip-permissions가 왜 위험한지(승인 게이트 생략)는 에이전트 개념의 승인 게이트 정본을 보세요. bypass 모드에서도 명시적 ask 규칙과 파괴적 삭제(rm -rf /·~)는 여전히 prompt가 뜨고(circuit breaker), 관리자는 disableBypassPermissionsMode로 모드 자체를 막을 수 있습니다.
여기까지가 권한 설계 → 강제 → 조직 고정 → 격리의 흐름입니다. 더 깊이 가려면: 사내 서버를 붙이는 MCP 커넥터, 그 전 단계인 Claude Code 실무, 에이전트 3원칙과 승인 게이트의 '왜'는 에이전트 개념, 무엇을 입력해도 되는지는 안전, 출력을 사람이 검증하는 절차는 검증 워크플로, 용어 정의는 용어집에서 이어집니다.