
Claude Code 권한 설계 실무 가이드 — 마찰(friction)을 없애면서도 안전망을 제거하지 않는 법
요약
Claude Code 사용 시 발생하는 권한 요청(permission prompt)의 마찰을 줄이면서도 보안 안전망을 유지하는 실무 가이드를 제공합니다. 단순한 허용(allow)을 넘어 안전한 실행 경로로의 교체라는 설계 원칙과 인자 제약 우회 방지법을 다룹니다.
핵심 포인트
- 권한 설계는 허가 추가가 아닌 안전한 실행 경로로의 교체여야 함
- Bash 등 명령어의 무분별한 allow 설정은 임의 코드 실행 위험 초래
- 인자 제약(argument constraint) 우회 및 env-runner 우회로 주의 필요
- settings.json의 scope 계층(user/project/managed) 이해 필수
Claude Code(AI 코딩 에이전트)를 정기적인 작업에 사용하기 시작하면, 거의 반드시 마주하게 되는 것이 바로 **permission prompt(허가를 요청하는 확인 다이얼로그. 이하 prompt)의 friction(마찰)**입니다. "이 명령어를 실행해도 될까?"라는 질문이 작업할 때마다 끼어듭니다. 번거롭기 때문에 Bash(*)를 allow에 넣어버리고 싶어지지만, 이는 스스로 안전망을 제거하는 행위이며, 후술할 내용처럼 임의 코드 실행(arbitrary code execution)을 그대로 통과시키는 입구가 됩니다.
이 기사는 "friction을 없애는 것"과 "안전망을 제거하지 않는 것"을 양립시키는 현장용 권한 설계 가이드입니다. 결론부터 말씀드리면, 권한은 "허가를 추가하는 것"이 아니라 "안전한 실행 경로로의 교체"로 설계해야 합니다. 본고의 동작은 2026년 6월 시점의 공식 문서(permissions·이하 doc)(macOS의 CLI 환경에서 확인)를 기준으로 합니다. 동작은 버전 및 빌드에 따라 달라질 수 있으므로, 마지막에는 자신의 환경에서 /permissions를 확인하여 검증하시기 바랍니다.
특히, 많은 권한 설정 가이드가 다루지 않는 **인자 제약(argument constraint) allow의 bypass(원칙 3)**와 **env-runner의 우회로(원칙 2)**는 안전망에 구멍을 내기 쉬우므로 중점적으로 다룹니다. 이는 allow/deny의 우선순위만으로는 막을 수 없는 부분입니다.
전제: 본고는 Claude Code의 권한 설정 파일인 settings.json(allow/deny 등을 작성하는 파일)을 편집할 수 있고, 현재 상태를 /permissions(Claude Code 대화 중에 입력하는 슬래시 명령어. 현재의 permission 설정 목록을 표시함)로 확인할 수 있음을 전제로 합니다. 설정에는 scope 계층이 있습니다 — user(~/.claude/settings.json) / project(<repo>/.claude/settings.json) / managed(조직 배포). 후술할 평가 순서나 "deny가 스코프를 넘어 allow를 무효화하는" 동작은 이 계층을 전제로 합니다. 또한 deny의 Read(.env)나 sandbox.filesystem은 대상 OS와 sandbox가 활성화된 환경을 전제로 합니다.
본고에서 다루는 내용:
- read-only의 friction 해소와 위험한 명령어의 deny / hook
- POSIX shell의 Bash - 퍼미션 모드는 기본값인
default(defaultMode를 명시적으로 설정하지 않은 상태) 단일 프로젝트 디렉토리
본고의 범위를 벗어나는 내용(별도 기사 「모드 편」 「deny / 경로 편」으로 분할):
- Edit/Write 툴
- Read/Edit의 경로 지정(
Read(.env)의 앵커 의미론 및 symlink) - MCP tool · subagent(
Agent(...)) - PowerShell
- 퍼미션 모드(
acceptEdits/plan/dontAsk/bypassPermissions) - 다중 디렉토리 운용 · 조직 managed 설정
이러한 구분은 "모두 다루는 척"하는 것을 피하기 위함입니다. 각 테마는 고유한 함정이 있으므로 독립적으로 다룰 가치가 있습니다.
또한 본고의 **원칙(설계 사상)**은 permission 모델을 가진 다른 AI 코딩 에이전트에도 응용할 수 있지만, 설정 파일, 명령어, 구문은 Claude Code 고유입니다(다른 에이전트에서는 동일한 개념으로 치환하여 이해해 주세요).
출발점: 공식 doc을 믿고 allow 했음에도 prompt가 뜨는 경우
권한 설계에서 처음에 깨달은 점은 어떤 괴리에 대한 인지였습니다. 공식 doc을 읽고 "이것은 read-only이므로 안전하다"라고 판단하여 allow에 나열했음에도, runtime에서는 prompt가 뜨는 경우가 있다는 사실입니다.
교훈은 단순합니다. 검증의 ground truth(실제 정답)는 doc이 아니라 runtime입니다. doc은 방침을 알려주지만, build / 버전 / 모드에 따른 실제 동작과는 미묘하게 차이가 날 수 있습니다. allow를 작성했다면, 반드시 실제로 실행하여 prompt가 사라졌는지 확인해야 합니다. 이것이 모든 전제가 됩니다. 본고의 claim(주장) 자체도 공식 doc을 근거로 하고 있는 이상, 예외는 아닙니다. 독자의 환경에서 반드시 동일한 동작이 일어난다고 보장할 수 없으므로, 마지막에는 자신의 /permissions를 확인하십시오.
실행을 통해 직접 확인하십시오 (이 기사를 공식 문서(doc)와 마찬가지로 의심하며 읽는 것이 올바른 사용법입니다).
한 가지 더, 복합 명령(composite command)의 대조를 숙지해 두어야 합니다. Claude Code는 셸 연산자(shell operator; &&, ||, ;, | 등)를 인식하며, 서브 명령(subcommand)별로 독립적으로 대조합니다. 예를 들어 cat access.log | grep " 500 " | awk '{print $7}' | sort | uniq -c (500번 에러 URL을 세는 전형적인 파이프라인)의 경우, 눈에 띄는 앞부분의 cat · grep은 내장된 read-only(후술할 원칙 1 참조)이므로 무죄이며, 확인을 요청하는 주체는 목록에 없는 awk · sort · uniq — 즉, **범인은 후반부의 토큰(token)**입니다. 따라서 "어디에서 확인을 요청받았는가"는 겉모습의 화려함이 아니라, 각 서브 명령이 내장된 read-only인지 여부로 구분해야 합니다.
나아가, 복합 명령을 "Yes, don't ask again"으로 승인하면, 서브 명령마다 별도의 허용(allow) 규칙이 저장됩니다. 공식 문서(doc)에는 "하나의 복합 명령으로부터 저장되는 규칙은 최대 5개"(서브 명령별로 최대 5개)라고 명시되어 있습니다.
원칙 1: 먼저 내장된 read-only를 파악할 것 (추가할 필요가 없음)
Claude Code는 일련의 Bash 명령을 내장된 read-only로 인식하여, 어떤 모드에서도 프롬프트(prompt) 없이 실행합니다. 공식적으로 나열된 것은 다음 14개와 read-only 형식의 git입니다.
ls cat echo pwd head tail grep find wc which diff stat du cd
+ read-only인 git (git status / git log 등)
즉, Bash(grep *)나 Bash(cat *)를 굳이 allow 목록에 적을 필요는 없습니다. allow 목록에 나열할수록 설정은 읽기 어려워지며, 정말로 제한해야 할 위험한 명령어가 묻히게 됩니다. "추가하기 전에, 추가하지 않아도 되는 것을 먼저 알 것".
하지만 두 가지 함정이 있습니다. 판단 기준은 "read-only 처럼 보이는가"가 아니라 "목록에 있는가"입니다.
- ① 목록에 없는 명령은 항상 확인 절차를 거칩니다.
sort,sed,awk는 read-only처럼 보일지라도 목록 외부에 있으므로,sort file과 같이 옵션 없이 사용해도 확인을 요청합니다. - ② 목록 내에 있더라도, 이들은 쓰기 및 실행도 가능하기 때문에
find나 read-only인git은 조건부 **위험 플래그(danger flag)**가 붙을 수 있습니다 (glob이 위험 플래그로 변할 수 있기 때문입니다. 특히-delete,-exec등). 또한 따옴표로 감싸지 않은 glob(unquoted glob; 따옴표를 쓰지 않은*, 예:find . -name *.py의*.py등)이 포함되면 read-only 취급에서 벗어나 확인을 요청합니다.find -exec/find -delete는Bash(find *)와 같은 전방 일치(prefix match) allow로도 막을 수 없으며 항상 확인을 거칩니다 (후술할 "exec 래퍼" 참조). 반대로ls와 같이 쓰기 및 실행 능력이 없는 명령은 glob이 위험해질 여지가 없으므로,ls *.ts와 같이 glob을 붙여도 확인을 요청하지 않습니다.
보충: read-only 판정의 세부 사항은 빌드나 버전에 따라 차이가 있을 수 있습니다 (공식 문서에서 read-only라고 명시한 명령이라도 환경에 따라 프롬프트가 나타날 수 있습니다). 그렇기에 allow를 작성했다면 런타임(runtime)에서 확인해야 합니다. read-only 용도는 후술할 Read 도구 경로를 사용하는 것이 확실합니다.
원칙 2: 임의 코드는 named-script + narrow allow (broad 허용 금지)
스크립트를 실행하고 싶을 때, Bash(python3 *)와 같은 broad(광범위한) 허용은 임의 코드 실행(arbitrary code execution)을 방치하므로 피해야 합니다. 대신, named-script(경로가 고정된 스크립트) + 해당 경로만 허용하는 narrow(좁은 범위의) allow 방식을 사용합니다.
// ❌ 위험: 임의의 Python을 확인 없이 실행할 수 있음
"allow": ["Bash(python3 *)"]
// ✅ 안전: 고정된 스크립트만 허용
...
좁은 범위의 허용(narrow allow)이 의도한 대로 작동하는지는 문서의 예측이 아니라 실제 환경에서 확인해야 합니다(런타임(runtime)이 Ground Truth이며, 후술할 계층 분리에서 말하는 실제 환경 관측입니다). 확인 관점은 두 가지이며, 이를 혼동하지 않는 것이 중요합니다.
① 희망 사항(마찰(friction) 제거)이 달성되었는가: 목표로 한 named-script에서 확인(confirmation) 절차가 사라지는가.
② 안전망이 남아 있는가 (너무 넓게 설정하지 않았는가): 그 외의 경우, 특히 위험한 명령어에서는 여전히 확인 절차가 나타나는가.
①만 보고 "확인이 사라졌으니 OK"라고 멈춰버리면, ②의 허점—본래 확인이 나타나야 할 명령어에서 확인이 나타나지 않는 상황—을 놓치게 됩니다. allow를 작성한 후에는 "의도한 것은 사라졌는가"와 "의도하지 않은 것은 남아 있는가"를 모두 확인해야 합니다.
래퍼(Wrapper)의 3가지 유형 (동작이 다르므로 구분 필요)
명령어 앞에 다른 명령어를 "씌우는" **래퍼(wrapper)**는 allow 대조(matching) 시 처리 방식이 세 가지로 나뉘며, 이를 혼동하면 보안 허점이 됩니다.
| 유형 | 예시 | 대조 시 처리 방식 | 함의 |
|---|---|---|---|
| 프로세스 래퍼 (Process wrapper) | timeout time nice nohup stdbuf + 플래그 없는 xargs | 내부를 벗겨내어 대조 | Bash(npm test *)가 timeout 30 npm test에도 매치됨 (허용 권한이 내부까지 전달됨 · 편리함) |
| env-runner (환경 래퍼) | devbox run docker exec npx mise exec | 벗겨내지 않고 내부를 대조하지 않음 | Bash(devbox run *)는 devbox run rm -rf .까지 통과시킴 ⚠ → runner+inner의 exact rule (예: Bash(devbox run npm test))을 inner 명령어별로 하나씩 작성해야 함 |
| exec 래퍼 | watch setsid ionice flock + find -exec / find -delete | prefix allow로는 억제할 수 없으며 항상 확인 절차 발생 | 허용하려면 완전 일치(exact match) 규칙을 작성해야 함 |
한마디로 요약하자면—프로세스 래퍼는 허용 권한이 내부까지 전달되고, env-runner는 전달되지 않고 그대로 통과시키며, exec 래퍼는 prefix 방식으로는 허용되지 않는다는 것입니다. 특히 env-runner가 그대로 통과되는 점은 놓치기 쉬운 허점입니다.
원칙 3: 인자 제약 allow는 사용하지 않는다 (너무 좁아서 신뢰할 수 없음)
"특정 URL만 허용"과 같은 인자(argument)를 제약하는 allow 패턴은 취약합니다. Bash(curl http://github.com/*)는 GitHub로 범위를 좁혔다고 생각할 수 있지만, 신뢰할 수 없습니다. 공식 문서는 이 규칙이 다음의 변형들에 매치되지 않는다(즉, 허용되지 않고 확인 절차가 나타난다)고 명시하고 있습니다.
- 옵션 선행:
curl -X GET http://github.com/...
→ 매치되지 않아 확인 절차 발생 - 프로토콜 차이:
curl https://github.com/...
→ 위와 동일 (http와https는 별개) - 리다이렉트 형태:
curl -L http://bit.ly/xyz
→ 위와 동일 (시작 부분이curl -L이므로 규칙에 맞지 않음) - 변수 확장:
URL=http://github.com && curl $URL
→ 위와 동일 - 불필요한 공백:
curl http://github.com
→ 위와 동일
즉, "범위를 좁혔다"고 생각한 것이 정당한 GitHub 요청(옵션 포함, https)까지 차단하는 반면, **규칙이 거의 기능하지 않음에도 "제한되어 있다"고 믿게 만드는 잘못된 안전감(false sense of security)**을 유발합니다. 실제로 끝에 공백이 없는 일반적인 curl http://github.com/repo조차 *의 단어 경계(word boundary) 때문에 차단됩니다(확인 절차가 발생합니다).
게다가 반대로, 규칙에 매치되는 형태라고 해서 반드시 안전한 것도 아닙니다.
curl http://github.com/ -L http://evil.example
(URL 뒤에 공백이 있으면 *가 매치됨)는 확인 절차 없이 실행되며, -L에 의해 다른 도메인으로 리다이렉트됩니다. 매치 판정 시 리다이렉트 대상을 확인하지 않기 때문입니다. 이것이 인자 제약 allow의 진정한 허점입니다.
보안 리서치(Flatt Security의 Claude Code approval bypass 8가지 수법)에서도 변수 확장(variable expansion)이나 히스토리 쓰기(history writing)를 통해 승인을 우회하는 수법이 제시되었습니다.
즉, 인자 제약 allow는 두 가지 실패를 동시에 안고 있습니다——① 너무 약해서 정당한 요청(options가 포함된 https 등)까지 차단함 (false sense) / ② 매치되더라도 리다이렉트(redirect) 대상을 확인하지 않고 통과시킴 (진정한 허점). 이 두 가지 요인 때문에, "안전하게 좁혔다"라는 착각이 가장 위험합니다.
URL을 제한하고 싶다면, 공식 권장 사항에 따라 다음과 같이 하십시오.
curl이나 wget 등을 deny로 차단하고, 네트워크는 WebFetch(domain:github.com)의 allow로 허용된 도메인만 통과시킨다.
- 또는 PreToolUse hook으로 URL을 검사하여 차단한다.
CLAUDE.md가이드는 "무엇을 시도할 것인가"를 형성할 뿐 경계를 강제하지 않으므로, 반드시 위 방법들과 병용해야 합니다 (CLAUDE.md는 에이전트에 대한 프롬프트 문맥(prompt context)이며, 실행 가능 여부를 판정하는 permission rule은settings.json측에서 담당합니다. 레이어가 다르기 때문에CLAUDE.md에 "curl은 github만 허용"이라고 적어도 rule로서 작동하지 않습니다).
※ 단, WebFetch는 GET 요청용이며, POST나 인증 헤더(authentication header)가 포함된 호출은 대체할 수 없습니다. 이것이 필요한 업무에서는 curl을 deny로 설정한 후, named-script + narrow allow(원칙 2) 또는 hook을 통해 개별적으로 허용합니다.
원칙 4: 차단은 exit 2 / PreToolUse hook (rule보다 우선)
"기본적으로는 모두 통과시키되, 이것만은 반드시 막고 싶다"를 실현하려면 hook을 사용합니다. PreToolUse hook이 exit code 2로 종료되면, permission rule을 평가하기 전에 도구 호출을 중단하며, 이는 allow rule보다 우선됩니다 (exit 1은 차단되지 않으므로 효과가 없습니다).
#!/usr/bin/env bash
# PreToolUse hook: 위험한 명령어만 exit 2로 중단
input=$(cat)
...
이 스크립트는 그대로는 작동하지 않습니다. 연결 작업이 필요합니다——① 실행 파일로 저장하고 chmod +x를 수행 (저장 위치는 임의로 정하되 ~/.claude/hooks/block-dangerous.sh와 같이 고정된 경로를 정해두어야 하며, jq가 필요함), ② settings.json의 hooks에 절대 경로로 등록합니다:
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "/Users/you/.claude/hooks/block-dangerous.sh" }] }
...
※ 위의 case 패턴도 문자열 매칭 방식이므로 rm -rf / (공백 차이)나 rm -fr / (순서 차이)는 놓칠 수 있습니다 (원칙 3의 인자 매칭 취약성과 동일한 형태). 진지하게 차단하려면 명령어를 정규화(normalize)한 후 대조해야 합니다.
여기서 서두의 경고와 언뜻 모순되어 보이는 공식 운영 패턴을 정리하겠습니다. Bash(*)의 전체 허용은 "hook을 겹쳐서 사용하는지 여부"에 따라 평가가 역전됩니다——서두에서 "안전망을 제거하는 입구"라고 부정했던 것은 hook으로 막지 않은 날것의 상태(임의의 코드가 그대로 통과됨)입니다. 반면, 공식이 제시하는 방식은 "allow에 Bash(*) 또는 Bash를 넣어 모든 Bash를 무검사로 두되, PreToolUse hook으로 막고 싶은 몇 가지만 거부하는" 형태입니다. 이는 hook이 안전망 역할을 하기 때문에 별개의 패턴입니다. 후자는 허용 리스트(allow list)의 비대화를 피하면서 안전망을 남겨둘 수 있습니다.
날것의 전체 허용은 피하고, 전체 허용을 한다면 반드시 hook을 겹쳐서 사용한다——이 조건이 두 가지를 구분합니다.
hook과 deny의 구분 사용: 정적인 패턴으로 "이것은 절대 금지"라고 단언할 수 있는 것은 deny에 집약합니다 (스코프를 가로질러 적용되며 가장 확실함). deny에 hook을 한 겹 더 겹쳐 사용하는 이유는 deny 자체가 무결하지 않기 때문입니다. 후술할 50-subcommand cap이나 경로 별칭(path alias)을 통해 deny가 우회되는 사례가 입증되었으며, deny의 정적 패턴으로는 작성할 수 없는 동적 판정(인자 내용이나 문맥에 따라 가부 결정)이나 누락을 보완하는 최후의 보루가 바로 hook입니다. 따라서 "절대로 막아야 하는" 것은 deny와 hook 두 겹을 겹쳐 사용합니다. 또한 ask에 의존할 수 없는 별도의 이유가 있는데, 아무도 화면을 보고 있지 않은 무인 운영(prompt를 승인할 사람이 없는 상황. dontAsk/bypassPermissions의 모드 변경과는 다름—모드는 별도 글에서 다룸)에서는 ask가 실질적으로 무효가 되기 때문에, 차단하는 선은 ask가 아닌 deny/hook으로 집약해야 합니다.
평가 순서는 다음과 같습니다.
deny → ask → allow. deny는 항상 최우선이며, user / project 등 스코프를 가로질러도 우선순위를 가집니다 (user 설정의 deny는 project 설정의 allow를 무효화함). 즉, "절대 금지"는 deny에 집약하면 어떤 설정에서도 실행되지 않습니다. managed settings(조직 배포)의 deny는 사용자 설정으로 뒤집을 수 없으므로, 팀에 배포할 계획이라면 deny를 managed 측에 두는 것이 안전합니다.
평가 순서를 도식화하면 다음과 같습니다 (위에 있을수록 우선순위가 높음).
다만 deny도 무결하지 않습니다. 해외 보안 검증에서는 ① 복합 명령의 서브커맨드가 50개를 초과하면 per-subcommand의 deny 체크가 중단되고 ask로 넘어가는 동작(MAX_SUBCOMMANDS_FOR_SECURITY_CHECK = 50
・무인 모드에서는 실질적으로 그대로 적용됨 / v2.1.90에서 무통보 수정됨), ② /proc/self/root/...와 같은 경로 별칭으로 deny 패턴을 회피하고, 나아가 sandbox에 가로막히면 에이전트 스스로 sandbox를 무력화하여 실행한 사례(2026-03)가 보고되었습니다. deny는 최우선적인 강력한 경계이지만 "이것만 쓰면 절대적이다"라고 할 수는 없습니다. **최소 권한(Least Privilege)·OS sandbox·hook을 겹치는 다층 방어(defense-in-depth)**를 전제로, deny를 "최후의 보루 중 한 겹"으로 사용하는 것이 실무적인 해법입니다.
원칙 5: 분석은 prompt가 발생하지 않는 경로로 유도할 것
friction의 상당 부분은 "파일을 읽거나 / 찾는" 처리에서 발생합니다. 이를 prompt가 발생하지 않는 도구(tool) 경로로 교체하는 것이 root-fix입니다.
⚠ 단, "유도하는 것"이
확실히 효과를 보려면 해당 경로를 기제(설정의 allow / PreToolUse hook)로 강제했을 때뿐입니다. "다음부터는 안전한 경로를 사용하겠다"라고 운영자(사람이든 에이전트든)의 규율에 의존하는 한, friction은 계속 남게 됩니다. 지시는 시도를 형성할 뿐 강제하지 못한다(원칙 4)는 점은 에이전트 자신의 행동에도 적용되기 때문입니다. 안전한 경로를 선택하게 만드는 것이 아니라 기제로 경로를 고정해야 합니다. 이 "규율이 아닌 기제로"의 철저한 적용(안전한 명령을 결정론적으로 자동 승인하는 hook 등)은 시리즈 후속편(자동화 편)에서 다룹니다.
- 파일 내용을 읽기 →
cat/sed를 호출하지 않고 Read 도구(read-only 취급 · prompt 없음) - 검색 → Grep / Glob 도구(위와 동일)
- 반드시 스크립트가 필요한 경우 → 원칙 2의 named-script
Read/Grep/Glob 도구 경로가 확인 없이 통과되는 이유는, 이것들이 Bash의 대조 과정을 거치지 않는 built-in tool이기 때문입니다 (Bash permission rule의 대상 외). 다만 경로를 바꾸더라도 deny는 작동합니다. Read(.env)의 deny는 Read 도구를 통한 접근도 차단하므로, "경로 교체 = 무엇이든 읽을 수 있음"을 의미하지는 않습니다. 주의할 점은, Read/Edit의 deny rule은 Bash 상의 cat, head, tail, sed에는 적용되지만, Python/Node 스크립트가 자체적으로 여는 파일에는 적용되지 않는다는 것입니다. 비밀 파일을 OS 레벨에서 보호하고 싶다면 sandbox(
sandbox.filesystem)를 병용합니다(단, sandbox 역시 이름 기반 대조 방식이므로 단독으로는 보장하지 않습니다. 앞서 언급했듯이 경로 별칭(path alias)을 통해 우회되는 사례가 증명되었으며, OS sandbox는 강력한 방어 수단이지만 다층 방어(multi-layered defense)의 일부로서 사용해야 합니다).
요약: settings.json의 before / after
실제 설정을 before/after로 비교하면 다음과 같습니다. before는 "허가를 추가한다"는 발상 때문에 비대해지며, 심지어 허점도 존재합니다.
// ❌ before: read-only를 불필요하게 나열 + 인자 제약(argument constraint) allow(취약) + 광범위한(broad) 허가
{
"permissions": {
...
// ✅ after: 추가하지 않음 + 좁은 범위의(narrow) allow + deny로 차단 + hook으로 안전망 구축
{
"permissions": {
...
after 방식이 더 짧고, 허점이 적으며, 마찰(friction)도 작습니다. "허가 목록을 키워나간다"는 발상에서 "안전한 경로로 교체하고, 위험 요소만 deny + hook으로 막는다"는 발상으로 전환해야 합니다. 이것이 제가 도달한 실무적인 해답입니다. 실제로, 이 after의 deny 구성(curl/wget, git의 파괴적 작업, deploy, 비밀 파일)은 제가 자신의 ~/.claude에 적용하여 매일 운용하고 있는 설정입니다.
보충:
git reset --hard를 deny에 넣은 이유는 git 조작을 에이전트(agent)에게 맡기는 운용 방식을 택하고 있기 때문입니다. 사용자가 자리에 있을 때는 프롬프트(prompt)로 멈출 수 있지만, 무인으로 실행하는 운용(아무도 프롬프트를 승인하지 않는 상황)에서는 "ask"가 아무도 보지 않으므로 기능하지 않으며, 오직 deny만이 파괴적인 동작을 막을 수 있습니다. 에이전트에게 더 많이 맡길수록 안전은 deny/allow의 경계로 수렴합니다. 이 점은 별도의 글(무인 운용 편)에서 심도 있게 다루겠습니다.
빠른 참조표: 명령어를 어떻게 허가할 것인가
"이 명령어를 허가하고 싶다"고 생각되면, 다음 트리 구조에 따라 설계하십시오.
| 상황 | 할 일 | 원칙 |
|---|---|---|
| 읽기 / 찾기 | Read / Grep / Glob 도구로 유도 (확인 없음) | 5 |
내장된 read-only (ls cat grep find 등) | allow에 추가하지 않음 (기본적으로 확인 없음) | |
목록 외 (sort sed awk ) | 항상 확인이 나온다는 전제로 설계 (glob 무관) | |
| 임의 스크립트 | named-script + 해당 경로만 허용하는 narrow allow | 2 |
env-runner (devbox run docker exec npx ) | runner+inner의 exact rule (broad allow는 그대로 통과됨) | |
| 특정 URL/인자만 허가 | 인자 제약 allow를 사용하지 않고 deny + WebFetch / hook 사용 | 3 |
| 반드시 막아야 함 | deny (최우선 순위·스코프 초월) + PreToolUse hook (exit 2) | 4 |
CTA
자신의 agent 설정을 /permissions에서 열어 다음 4가지 사항을 점검해 보십시오. 수정할 때의 완성된 형태는 위의 "요약: settings.json의 before / after"를 그대로 템플릿으로 사용하면 됩니다.
- 내장된 read-only를 불필요하게 allow하고 있지 않은가 (삭제할 수 있는 행이 있을 것입니다)
- 인자 제약 allow(특정 URL/경로로 한정한 allow)를 사용하고 있지 않은가 (deny + WebFetch/hook으로 교체하십시오)
- 광범위한(broad) 허가(예:
Bash(*)/python3 *등을 임의의 코드에 부여하는 것)를 주고 있지 않은가 (named-script로 전환하십시오) - 반드시 막아야 하는 명령어를 PreToolUse hook (exit 2)으로 차단하고 있는가
그리고 무엇보다, allow를 작성했다면 런타임(runtime)에서 확인하십시오. 문서가 아니라 실제 동작이 정답입니다. 확인은 어렵지 않습니다. Claude Code를 실행하고, 대상 명령어를 포함하는 지시를 한 번 내린 뒤, 프롬프트가 뜨는지 혹은 사라졌는지를 실제로 관찰하기만 하면 됩니다 ("의도한 것은 사라졌고", "의도하지 않은 것은 남아 있다"를 모두 확인하십시오 · 원칙 2).
Discussion

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