
Claude Code를 팀에 배포할 때의 settings.json — deny / ask / allow를 어떻게 설정했는가
요약
Claude Code를 팀 단위로 배포할 때 보안과 안정성을 확보하기 위한 settings.json 설정 전략을 다룹니다. deny, ask, allow 권한 체계의 평가 순서를 설명하고, 간접 프롬프트 인젝션과 같은 위협으로부터 시스템을 보호하기 위한 설계 원칙을 제시합니다.
핵심 포인트
- Claude Code 설정의 스코프 우선순위(Managed > Local > Project > User) 이해
- 권한 평가 순서는 deny → ask → allow 순으로 진행됨
- deny 규칙은 어떤 스코프에서도 allow보다 우선하여 적용됨
- 간접 프롬프트 인젝션 방지를 위한 '봉쇄' 중심의 설계 필요
CLI를 사용하지 않는 멤버를 포함한 팀에 Claude Code를 배포하게 되어, 배포용 settings.json을 설계했습니다. 이 기사는 해당 설정을 deny / ask / allow 중 어디에 무엇을 두었는지, 왜 그렇게 했는지에 대한 근거와 함께 공개하는 글입니다.
대상은 팀에 Claude Code를 배포하는 측의 사람입니다. 정보 시스템(情シス) 부서가 없는 소규모 조직을 상정하고 있습니다. 여러분이 가져갔으면 하는 것은 설정의 복사본이 아니라, 「왜 allow가 아니라 ask인가」를 스스로 설명할 수 있는 상태입니다.
프롬프트 작성법, 코드 품질, 에이전트 설계는 다루지 않습니다. 「배포했을 때 망가지지 않도록 하는 것」까지가 범위입니다.
먼저, 가장 중요한 전제
설정 이야기를 하기 전에 한 가지 확실히 해두겠습니다.
사용자에게 settings.json을 배포하는 방식은 강제 사항이 아닙니다.
Claude Code의 설정 파일에는 스코프(Scope)가 있으며, 우선순위가 정해져 있습니다.
| 스코프 | 위치 | 우선순위 |
|---|---|---|
| Managed | 관리자가 배포 (MDM / OS 정책 / server-managed settings 등) | 최고. 다른 어떤 스코프로도 덮어쓸 수 없음 |
| Local | .claude/settings.local.json | 2 |
| Project | .claude/settings.json | 3 |
| User | ~/.claude/settings.json | 최저 |
배포용 파일을 각자의 ~/.claude/settings.json에 두도록 하는 방식은, 가장 우선순위가 낮은 스코프에 두는 것을 의미합니다. 본인이 에디터로 열어서 몇 줄 지워버리면 그것으로 끝입니다.
이것이 「의미가 없다」는 뜻은 아닙니다. 악의 없는 사고를 방지하는 용도로는 충분히 기능합니다. 실제로 저희가 상정한 것은 공격자가 아니라, 「잘 모르는 상태에서 Enter를 눌러버리는」 상황이었습니다. 그런 상황에는 효과가 있습니다.
단, 정말로 강제하고 싶다면 managed settings를 사용해야 합니다. 조직적으로 배포할 수 있는 환경이 있다면 처음부터 그쪽을 검토하십시오. 이 기사의 설정 내용은 그대로 managed settings에도 적용할 수 있습니다.
참고로 permission rules만은 다른 설정과 동작이 다르며, 스코프를 넘나들며 「덮어쓰기」가 아닌 「머지 (Merge)」됩니다. 그리고 deny는 어떤 스코프의 것이든 allow보다 먼저 평가됩니다. 사용자 설정의 deny가 프로젝트 설정의 allow를 무효화하는 방향도 성립합니다.
평가 순서를 먼저 파악하기
설정을 읽기 전에 이것만은 필요합니다. 규칙은 deny → ask → allow 순으로 평가되며, 가장 먼저 매치되는 것이 결과를 결정합니다. 규칙의 구체성 (Specificity)은 순서에 영향을 주지 않습니다.
즉, 다음과 같이 작성해도 예외가 되지 않습니다.
{
"permissions": {
"deny": ["Bash(aws *)"],
...
aws s3 ls는 deny 측에 먼저 매치되므로 차단됩니다. deny에 예외를 두는 것은 불가능합니다. 「기본은 금지, 이것만 허용」을 deny로 작성하려고 하면 반드시 실패합니다.
배포한 설정의 골격
전문을 이 기사에서 모두 공개하지는 않습니다 (후술할 이유에 의함). 골격은 다음과 같습니다.
{
"permissions": {
"defaultMode": "default",
...
이하, 블록별로 근거를 작성하겠습니다.
왜 「방지」가 아니라 「봉쇄」인가
설계 방침을 먼저 제시합니다. 상정한 핵심 위협은 간접 프롬프트 인젝션 (Indirect Prompt Injection)입니다.
Claude가 읽은 웹 페이지나 파일 안에 「다음 지시를 따를 것」이라고 적혀 있고, 모델이 이를 지시로 취급해 버리는 종류의 공격입니다. 공식 문서도 안전 대책을 나열한 뒤 다음과 같이 적고 있습니다.
While these protections significantly reduce risk, no system is completely immune to all attacks.
이 내용을 읽고 나서, 입력을 검열하여 "방지"하는 방향으로는 기울이지 않았습니다. 외부에서 읽어오는 내용을 전부 신뢰할 수 없다는 전제에 서면, 입구를 막는 설계는 언젠가 무너지게 됩니다. 읽게 하는 것을 줄이면 도구로서 사용할 수 없게 됩니다.
대신 채택한 것은 "속더라도, 할 수 있는 일이 제한되어 있다면 괜찮다"라는 사고방식입니다. 위험한 것은 모델이 속는 것 자체가 아니라, 속은 모델이 강력한 권한을 가지고 있다는 것입니다. 그래서 권한 측면을 제한합니다.
제한 대상은 세 가지로 나누어 생각했습니다.
- 읽을 수 있는 범위 — 무엇을 읽히면 곤란한가
- 쓸 수 있는 범위 — 무엇을 수정당하면 곤란한가
- 외부로 내보낼 수 있는 범위 — 무엇을 전송하면 곤란한가
이 중 3번이 가장 우선순위가 높다고 판단했습니다. 읽히기만 한다면 피해는 로컬에 머물지만, 전송되어 버리면 돌이킬 수 없습니다. 이후의 설정은 이 순서대로 적용하고 있습니다.
deny에 둔 것
deny는 "예외 없이 통과시키지 않음" 프레임입니다. 예외를 둘 수 없는 이상, 여기에 두는 것은 전혀 허용할 의사가 없는 것뿐입니다.
mcp__*로 일괄 deny 하기
MCP를
"deny": ["mcp__*"]
deny / ask 규칙은 도구 이름 위치에 글로브(glob)를 쓸 수 있습니다. mcp__*는 모든 서버의 모든 MCP 도구에 매칭됩니다.
일괄 deny로 설정한 이유는 MCP가 계속 늘어나는 것이기 때문입니다. 개별적으로 "이것은 위험하다"며 하나씩 차단하는 방식은, 새로운 커넥터가 늘어날 때마다 설정을 추적해야 하며, 추적을 놓친 순간 보안 구멍이 생깁니다. 기본적으로 전부 막아두고 필요한 것만 추가하는 방식이, 방치했을 때의 피해 규모 면에서 더 안전합니다.
여기서 주의할 점이 있습니다. mcp__*를 allow 측에 써서 허용을 되돌릴 수는 없습니다.
"*", "B*" 또는 "mcp__*"와 같이 앵커(anchor)가 지정되지 않은 allow glob는 경고와 함께 스킵되며 아무것도 자동 승인하지 않습니다.
allow에서 글로브를 사용할 수 있는 것은, mcp__<서버명>__와 같이 구체적인 서버 이름까지 리터럴(literal)로 작성한 뒤뿐입니다. mcp__github__get_*는 유효하지만, mcp__*는 경고와 함께 무시됩니다. 서버 이름을 쓰게 함으로써 "어떤 서버를 신뢰했는지"를 설정상에 명시하도록 설계되어 있습니다.
bypassPermissions를 봉쇄하기
"disableBypassPermissionsMode": "disable"
bypassPermissions는 권한 확인을 통째로 건너뛰는 모드입니다. 공식 설명도 이를 뒷받침합니다.
bypassPermissions는 프롬프트 인젝션 (Prompt Injection)이나 의도하지 않은 동작에 대해 아무런 보호를 제공하지 않습니다.
지금까지의 설계를 전부 무효화할 수 있는 스위치가 수중에 남아 있는 상태는 피하고 싶기에, 봉쇄했습니다. 값은 문자열 "disable"입니다.
단, 앞서 언급했듯이 사용자 설정에 둔 이것은 본인이 삭제할 수 있습니다. 공식 문서에서도 "managed settings에서 사용하는 것이 가장 효과적"이라고 명시하고 있습니다.
이것들은 사용자가 재정의할 수 없는 managed settings에서 사용할 때 가장 유용합니다.
같은 형태로 disableAutoMode도 있습니다. auto 모드에 대해서는 당초 "무효화한다"는 안으로 작성했으나, 최종적으로는 각자의 재량에 따라 사용해도 좋다로 변경했습니다. 이유는 책에서 다루겠습니다.
ask에 둔 것
이 부분이 이 글에서 가장 전달하고 싶은 핵심입니다.
"ask": [
"Bash(curl *)",
"Bash(wget *)",
...
"ask 같은 게 왜 필요한가"에 대한 답변
먼저 사실 관계를 정리하겠습니다. curl이나 wget은 별도의 설정을 하지 않아도 기본적으로 확인 절차를 거칩니다.
curl 및 wget과 같이 웹에서 콘텐츠를 가져오는 명령은 기본적으로 자동 승인되지 않습니다. 다른 읽기 전용이 아닌 Bash 명령과 마찬가지로 프롬프트가 나타납니다.
그렇다면 명시적으로 ask 규칙을 작성하는 의미는 무엇일까요? 모드를 관통하기 때문입니다.
권한 모드에는 auto (분류기(Classifier)가 백그라운드에서 판단하여 확인 과정을 생략함)나 bypassPermissions (모든 과정을 건너뜀)가 있습니다. 이러한 모드에서도, 명시적인 ask 규칙만은 확인을 강제합니다.
Explicit ask rules and connector tools your organization set to ask still force a prompt in this mode.
즉, ask 규칙은 "기본 동작을 그대로 따라 하는 무의미한 줄"이 아니라, 모드가 완화되더라도 벗어나지 않는 바닥입니다. auto 모드를 각자의 재량에 맡기는 판단을 내릴 수 있었던 것은 바로 이 바닥이 있었기 때문입니다.
왜 allow가 아닌가
이것들을 allow에 넣으면, "외부로 내보낼 수 있는 범위"가 확인 없이 열리게 됩니다. 게다가 allow는 모드를 관통하지 않습니다.
Allow rules have no effect in bypassPermissions because everything else is already approved.
그리고 auto 모드로 들어가면, 지나치게 넓은 allow 규칙은 자동으로 무효화됩니다. Bash(*)나 Bash(python*)와 같이 임의의 코드 실행을 허용하는 규칙, 패키지 매니저의 run 명령, Agent의 allow 규칙이 대상입니다. Bash(npm test)와 같이 좁은 범위의 규칙은 남습니다.
allow를 두텁게 설계한다는 것은, 모드가 바뀌면 의미도 바뀐다는 뜻입니다. 반면 ask를 두텁게 설계하는 방식에는 그러한 불안정성이 없습니다.
ask를 너무 늘리지 않기
그렇다고는 해도 ask를 늘릴수록 확인 절차가 많아지며, "일단 전부 Enter"를 누르는 방식으로 퇴화하게 됩니다. 이는 설정의 문제가 아니라 인간의 문제이므로, 설정 측면에서는 해결할 수 없습니다.
경계선은 "외부로 나가는 경로인가"를 기준으로 그었습니다. curl / wget은 명확하게 외부 지향적입니다. npx는 레지스트리로부터 임의의 코드를 가져와 실행하므로, 외부 지향과 실행 모두에 해당합니다. 반대로, 로컬 내에서 완결되는 작업은 ask에 포함하지 않았습니다.
인증 정보의 경로를 보호하기 — 이 부분이 가장 실수하기 쉽습니다
프로젝트에서 맡은 키(Key)나 클라우드 인증 정보가 로컬에 있다는 전제하에, 읽기 경로를 deny 했습니다. 그리고 여기서 두 번 실수했습니다.
실수 1: 맨 앞의 슬래시(/)는 절대 경로가 아니다
Read / Edit 규칙의 경로 지정은 .gitignore 표기법을 따르며, 4가지 패턴이 있습니다.
| 표기법 | 의미 |
|---|---|
//path | 파일 시스템의 루트(Root)로부터의 절대 경로 |
~/path | 홈 디렉토리로부터 |
/path | 설정 파일이 위치한 곳으로부터의 상대 경로 |
path / ./path | 현재 디렉토리로부터 |
세 번째가 함정입니다. 공식 문서에도 경고로 기재되어 있습니다.
A pattern like /Users/alice/file isn't an absolute path. The single leading slash anchors at the settings source, not the filesystem root.
나아가, 사용자 설정(User settings)에 작성했을 경우의 해결 위치가 명시되어 있습니다.
A deny rule such as Read(/secrets/**) in user settings blocks ~/.claude/secrets/**, not a secrets directory in your project.
즉, ~/.claude/settings.json에 Read(/Users/foo/.ssh/**)라고 적으면, 그것은 ~/.claude/Users/foo/.ssh/**를 가리키게 됩니다. 존재하지 않는 경로를 보호하고 있는 상태가 되며, 작성자는 보호했다고 착각하게 됩니다. 에러도 발생하지 않습니다.
올바른 방법은 슬래시가 두 개인 Read(//Users/foo/.ssh/**) 또는 홈 상대 경로인 Read(~/.ssh/**)입니다.
실수 2: deny는 "Claude의 도구"에만 적용된다
이 부분이 훨씬 더 중대합니다.
Read 및 Edit deny 규칙은 Claude의 내장 파일 도구(built-in file tools)와 Claude Code가 Bash에서 인식하는 cat, head, tail, sed와 같은 파일 명령에 적용됩니다. 스스로 파일을 여는 Python 또는 Node 스크립트와 같이 파일을 간접적으로 읽거나 쓰는 임의의 서브프로세스(subprocess)에는 적용되지 않습니다.
Read의 deny가 적용되는 것은 내장 파일 도구와 Claude Code가 인식할 수 있는 cat / head / tail / sed 등입니다. Python이나 Node 스크립트가 파일을 여는 경로에는 적용되지 않습니다.
deny 규칙으로 키 파일을 보호했다고 생각하더라도, 스크립트 하나만 작성되면 읽을 수 있습니다. OS 레벨에서 차단하고 싶다면, 공식 안내에 따라 샌드박스(sandbox)를 활성화해야 합니다.
특정 경로에 대한 모든 프로세스의 접근을 차단하는 OS 레벨의 강제 적용을 원한다면, 샌드박스를 활성화하세요.
이 두 가지를 모른 채 작성한 deny 규칙은, 작성한 본인만이 보호받고 있다고 착각하게 만드는 규칙이 됩니다. 저도 그랬습니다.
참고: 작동하지 않는 규칙을 작성해도 조용히 무시됩니다
Write(path)나 Glob(path)와 같이 경로가 포함된 규칙은 수락은 되지만 참조되지는 않습니다. 파일 권한은 Edit(path)와 Read(path)만으로 판정됩니다. 시작 시 경고가 발생하지만, 이를 간과하면 그대로 끝입니다.
Permission deny rule (.claude/settings.json): Write(docs/**) is not matched by
file permission checks — only Edit(path) rules are. Use Edit(docs/**) instead
Write(...)가 아니라 Edit(...)를 사용하세요.
흔히 보이는 설정 예시의 위험 요소
소개 기사들의 설정 예시에서 자주 보이는 유형 3가지를 꼽겠습니다. 특정 기사를 지칭하는 것이 아니라, 빈번하게 나타나는 형태에 대한 이야기입니다.
1. Bash(curl*)와 아스타리스크(*)의 위치
- 공백의 유무에 따라 의미가 달라집니다.
* 앞의 공백이 중요합니다: Bash(ls *)는 ls -la에는 매치되지만 lsof에는 매치되지 않는 반면, Bash(ls*)는 둘 다에 매치됩니다.
Bash(curl*)는 curl로 시작하는 다른 명령어도 함께 매치됩니다. 반대로 allow 측에서 이렇게 설정하면 의도하지 않은 명령까지 허용하게 됩니다. 끝부분에 한해서는 Bash(ls:*)가 Bash(ls *)와 동일하지만, Bash(git:* push)와 같이 중간에 작성한 :는 단순한 문자로 취급되어 매치되지 않습니다.
2. allow와 deny의 이진 구조로 구성됨
ask가 없는 설정은 확인 후 승인한다는 중간 선택지를 버린 것입니다. 이렇게 운영하면 반드시 "매번 멈춰서 번거롭다"는 상황이 발생하며, allow 규칙이 비대해집니다. 앞서 언급했듯이 allow는 모드를 관통하지 않으므로, 규칙이 커질수록 설계는 취약해집니다.
3. 인증 정보의 deny 경로가 작동하지 않음
앞 절에서 설명한 내용과 같습니다. 작성된 내용 자체가 안심할 수 있는 근거가 되지 않으므로, 자신의 설정을 다시 검토할 때는 "이 패턴이 정확히 어디를 가리키고 있는가"를 한 줄씩 확인해야 합니다.
검증 환경
- 기사 중의 사양 기술은 집필 시점의 공식 문서(Permissions / Permission modes / Settings / Security)를 통해 확인했습니다.
- 대상은 macOS 데스크톱 앱 사용을 전제로 합니다. Windows는 검증되지 않았습니다.
- Claude Code의 권한 관련 사항은 변경 속도가 빠른 영역입니다. 설정을 적용하기 전에 반드시 자신의 환경과 버전에서 동작을 확인하세요.
이 기사에서 다루지 않는 것
이 기사는 "설정 방식"에 집중했습니다. 실제로 배포하고 운영하기 위해서는 이것만으로는 부족합니다.
비엔지니어 대상 배포 절차 — 숨겨진 폴더에 파일을 배치하도록 하는 가이드라인을 어떻게 작성할 것인가. 터미널을 사용하지 않고 완료하는 방법
운영 규칙 — 설정 파일로는 표현할 수 없어 팀의 합의로 결정해야 했던 것들
프로젝트 폴더 단위의 가드 — 프로젝트별 .claude/settings.json 사용법과 사용자 설정과의 충돌
배포용 템플릿 전문 및 도입 후 점검 체크리스트
이러한 내용들은 동일한 1차 경험을 바탕으로 정리한 본문에서 다루고 있습니다. 이 기사의 내용만으로도 설정 설계는 가능하므로, 필요할 때 참조하시기 바랍니다.
마지막으로 다시 한번 강조합니다. 설정을 작성했다면, 의도한 대로 거부(deny)되는지 반드시 직접 확인하세요. 이 기사에서 언급한 두 가지 실수는 모두 "작성했음에도 적용되지 않는" 유형이며, 직접 확인하지 않으면 알아차릴 수 없습니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기