Claude Code 권한 게이트 해부: allow/deny/ask는 어떻게 갈리나

Claude Code에게 "이 파일 지워줘"를 시켰다고 하자. 어떤 요청은 바로 실행되고, 어떤 요청은 "정말요?"라고 되묻고, 어떤 요청은 아예 거절당한다. 이 차이는 모델의 기분이 아니라 Claude Code가 공개한 권한 규칙, 훅, 실행 모드에서 나온다. 겉보기에는 확인창 하나지만 실제 판정에는 규칙 우선순위, 도구 입력, 경로, 실행 환경이 함께 관여한다.
층이 많은 이유는 단순하다. "허용해도 되는가"라는 질문 하나로는 실제 위험을 다 못 잡는다. 명령어 자체는 허용 목록에 있어도 뒤에 붙은 옵션이 위험할 수 있고, 사람이 답할 수 없는 자동 실행에서는 확인질문 자체가 중단 조건이 된다. 이 글은 Claude Code 권한 문서, 훅 문서, 비대화형 실행 문서에서 확인할 수 있는 동작을 기준으로 allow·deny·ask의 갈림길을 구체적으로 설명한다.
허용·거부·확인질문, 세 갈래로 갈리는 이유
사람이 비서에게 심부름을 시키는 상황을 떠올려 보자. "우편함 확인해줘"는 그냥 시킨다. "내 통장에서 돈 빼줘"는 절대 안 시킨다. "이 이메일 답장 보내줘"는 초안을 보여달라고 한 번 확인한다. 세 가지 반응이 다 다르다. AI 에이전트의 명령 실행도 똑같다. 읽기 전용 조회는 곧장 허용하고, 되돌릴 수 없는 파괴적 작업은 아예 막고, 애매한 중간지대는 사람에게 한 번 물어본다.
이걸 이진 판정(허용/거부 둘 중 하나)으로 설계하면 둘 중 하나가 망가진다. 안전 쪽으로 몰면 매번 다 물어봐서 에이전트가 쓸모없어진다. 편의 쪽으로 몰면 위험한 명령까지 조용히 통과한다. 확인질문이라는 세 번째 갈래가 있어야 이 딜레마가 풀린다. 위험도가 낮으면 자동으로, 위험도가 확실히 높으면 차단으로, 판단이 갈리는 회색지대만 사람에게 넘긴다.
중요한 건 이 판정이 한 곳에서 한 번에 끝나지 않는다는 점이다. 명령이 실행되기까지 최소 두 개의 검사 층을 통과한다. 첫 번째 층은 규칙 기반의 일반 판정, 두 번째 층은 명령 내용을 직접 뜯어보는 세부 검사다. 두 층이 따로 있는 이유는 다음 장에서 설명한다.
규칙 문서로 미리 정하는 1차 판정
1차 판정은 미리 정해둔 규칙표를 찾아보는 일에 가깝다. Claude Code에서는 /permissions 화면이나 설정 파일의 permissions.deny·permissions.ask·permissions.allow에 도구와 패턴을 적는다. 평가는 deny → ask → allow 순서이며 먼저 일치한 규칙이 결과를 정한다. 구체성은 이 순서를 뒤집지 않는다. 예를 들어 넓은 Bash(aws *) deny와 좁은 Bash(aws s3 ls) allow가 동시에 맞아도 deny가 이긴다. ask와 allow가 함께 맞으면 ask가 먼저라 확인창이 뜬다.
규칙은 저장 위치와 수명도 구분해야 한다. Bash 확인창에서 “다시 묻지 않기”로 승인한 규칙은 저장되어 이후 세션에도 적용될 수 있지만, 파일 수정 승인은 문서상 세션이 끝날 때까지만 유지된다. 따라서 한 번 누른 허용을 모든 도구에 영구 적용되는 전역 결정으로 이해하면 안 된다. 실제 운영에서는 어떤 규칙이 어느 settings.json에서 왔는지 /permissions로 확인해야 예전에 만든 허용이 뜻밖의 작업에 재사용되는 일을 줄일 수 있다.
여기서 자주 나오는 오해가 하나 있다. 규칙표에 명령어 이름 하나만 등록해두면 충분하다고 생각하는 것이다. 실제로는 그렇지 않다. 파일 삭제 도구를 허용 목록에 올렸다고 해서 그 도구로 시스템 전체를 지우는 명령까지 안전한 건 아니다. 도구 단위 허용과 명령 내용 단위 위험은 서로 다른 축이다. 이 간극을 메우는 게 다음 층의 역할이다.
또 하나, 실행 환경도 이 판정에 영향을 준다. Claude Code의 비대화형 모드는 claude -p로 실행한다. 이 모드에서는 --allowedTools, permissions.allow, 또는 적절한 권한 모드로 미리 승인되지 않은 도구가 호출되면 사람이 답할 확인창을 무기한 기다리는 대신 실행이 중단된다. 예를 들어 --permission-mode acceptEdits는 파일 수정과 일부 파일 시스템 명령을 자동 승인하지만, 다른 셸 명령이나 네트워크 요청까지 자동 승인하지는 않는다. CI에서 멈춤을 피하려면 필요한 도구 범위를 실행 전에 정확히 선언해야 한다.
같은 명령도 내용을 뜯어봐야 하는 2차 검사
규칙표 조회가 끝나도 아직 안심할 수 없다. 특히 자유 형식 셸 명령을 실행하는 Claude Code의 Bash 도구는 호출 내용까지 규칙과 대조한다. 권한 문법은 Bash(npm run build)처럼 정확한 명령을 지정하거나 Bash(npm run test *)처럼 공백 뒤 와일드카드로 접두 범위를 정한다. &&, ||, 세미콜론, 파이프 등으로 묶은 복합 명령은 각 하위 명령이 독립적으로 허용되어야 전체가 통과한다. 도구 이름 하나만 허용하는 것과 실행 문자열을 제한하는 것이 다른 이유다.
비유하면 이렇다. 회사 정책에 "칼을 쓸 수 있다"는 규칙이 있다고 치자. 그런데 그 칼로 사과를 깎는지 타이어를 찢는지는 정책 이름만 봐서는 모른다. 실제로 뭘 하려는지 동작 하나하나를 봐야 안다. 셸 명령 검사가 하는 일이 이거다. 삭제 옵션이 강제로 걸려 있는지, 되돌릴 수 없는 초기화 명령인지, 파일 시스템의 민감한 경로를 건드리는지를 명령 문자열 자체에서 다시 분류한다.
경로 검사도 같은 결의 문제다. Claude Code는 심볼릭 링크를 읽을 때 링크 경로와 링크가 가리키는 실제 대상, 두 경로를 검사한다. allow 규칙은 링크와 대상이 모두 일치해야 자동 허용되고, 하나라도 벗어나면 확인을 요청한다. 반대로 deny 규칙은 링크나 대상 중 하나만 일치해도 차단한다. 예를 들어 허용된 ./project/** 안의 링크가 거부된 ~/.ssh/**를 가리키면, 표면 위치가 프로젝트 안이어도 대상이 deny에 걸려 차단된다. 단순히 “실제 경로도 본다”보다 이 비대칭을 알아야 allow 우회와 deny 누락을 함께 막을 수 있다.
정적 규칙 앞에는 PreToolUse 훅도 개입할 수 있다. 이 훅은 Claude가 도구 인자를 만든 뒤 실제 호출을 처리하기 전에 실행되며, allow·deny·ask·defer 중 하나를 반환하거나 입력을 수정할 수 있다. 예를 들어 Bash 호출의 tool_input.command를 검사해 rm -rf가 포함되면 permissionDecision: "deny"를 반환하도록 만들 수 있다. 훅이 아무 판단 없이 종료되면 자동 허용되는 것이 아니라 기존 권한 흐름으로 넘어간다. 따라서 PreToolUse는 권한표를 대체하는 만능 스위치가 아니라, 실제 입력을 보고 더 엄격한 정책을 앞단에 덧붙이는 검사 지점이다.
이 두 번째 층이 왜 필요한지 정리하면, 1차 규칙은 "이 도구를 써도 되는가"를 묻고, 2차 검사는 "이번에 실제로 하려는 일이 안전한가"를 묻는다. 질문이 다르니 검사도 따로 있어야 한다. 하나만 있으면 나머지 절반의 위험이 그대로 새나간다.
확인질문은 누구에게 가는가
확인질문 갈래로 넘어가면 새로운 문제가 생긴다. "누구에게 물어볼 것인가"다. 사람이 화면 앞에 앉아 직접 시킨 작업이면 답은 간단하다. 그 사람에게 팝업을 띄우면 된다. 그런데 요즘 에이전트는 혼자 일하지 않는다. 상위 에이전트가 여러 하위 에이전트에게 작업을 나눠주는 구조, 즉 조율자와 작업자로 나뉜 위임 구조가 흔하다.
이 구조에서는 승인자가 고정돼 있지 않다. 하위 작업자가 위험한 명령을 요청하면, 그 승인 권한이 화면 앞의 사용자에게 갈 수도 있고, 작업을 배분한 상위 에이전트(조율자)에게 갈 수도 있고, 미리 정해둔 정책에 따라 자동으로 결정될 수도 있다. 요청을 만든 주체와 승인할 권한을 가진 주체가 다르다는 뜻이다. 이걸 요청 주체와 승인 주체로 나눠서 생각하면 명확해진다. 하위 작업자는 "이거 해도 되나요"라고 요청만 하고, 실제 승인 여부는 그 요청을 받을 자격이 있는 상위 계층에서 결정한다.
왜 이렇게 복잡하게 만들까. 위임 구조에서 승인 권한까지 하위 작업자에게 넘겨버리면, 여러 작업자가 동시에 서로 다른 위험한 명령을 각자 알아서 승인하는 상황이 생긴다. 통제가 사라진다. 승인 권한을 한 군데로 모아두면, 여러 작업자가 동시에 뭘 하고 있는지 한눈에 보이고 위험한 순간에 개입할 지점도 명확해진다. 그래서 이런 구조에서는 요청 처리 로직과 승인 처리 로직을 아예 다른 코드 경로로 분리해둔다. "인터랙티브 사용자용 확인질문"과 "위임 흐름용 확인질문"이 이름부터 다른 별개의 처리기로 존재하는 이유다.
여기에 감사 로그가 붙는다. 누가 요청했고 누가 승인했는지, 확인질문이 뜬 시점과 답변 시점을 기록해야 나중에 "이 명령이 왜 통과됐지"를 되짚을 수 있다. 실시간 확인만 하고 기록을 안 남기면, 사고가 난 뒤에 원인을 추적할 방법이 없다. 승인은 그 순간의 판단이지만 로그는 그 판단을 나중에 검증할 수 있는 유일한 증거다.
허용됐어도 어디까지 손댈 수 있는지는 다른 문제다
여기까지 "실행해도 되는가"를 다뤘다. 그런데 이것과는 결이 다른 질문이 하나 더 남는다. "실행을 허락하더라도, 어디까지 손댈 수 있게 둘 것인가"다. 이게 실행 격리(샌드박스) 영역이다.
비유하면, 손님에게 주방 출입을 허락하는 것과 냉장고 문을 다 열어두는 건 다른 결정이다. 요리를 허락했다고 냉동실 안쪽 재료까지 마음대로 쓰게 둘 필요는 없다. AI 에이전트도 마찬가지다. 어떤 명령이 실행 가능 판정을 받았다고 해서 파일 시스템 전체, 네트워크 전체에 자유롭게 접근하게 둘 이유는 없다. 작업에 필요한 최소 범위로 실행 공간 자체를 좁혀두고, 그 공간 밖으로 나가려는 시도는 권한 판정과 별개로 한 번 더 막는다.
그래서 실행 격리는 "허용/거부/확인질문" 3단 분기와 나란히 있는 또 하나의 축이다. 판정 로직이 "이 명령을 실행해도 되는가"를 정한다면, 격리 로직은 "실행되는 동안 어디까지 볼 수 있고 어디까지 바꿀 수 있는가"를 정한다. 둘을 하나로 섞으면 설계가 꼬인다. 판정은 허락 여부의 문제이고 격리는 반경의 문제라서, 판정 통과 후에도 격리 경계는 독립적으로 다시 적용돼야 한다.
이 구조를 실제로 접하고 나서 생각이 하나 바뀌었다. "위험한 명령을 막는다"는 목표를 한 겹의 확인창으로 해결하려던 게 애초에 무리한 접근이었다. 겹겹이 쌓인 이유는 각 층이 서로 다른 질문에 답하기 때문이다. 규칙표는 정책 질문에, 명령 내용 검사는 실제 동작 질문에, 승인자 결정은 권한 소재 질문에, 실행 격리는 반경 질문에 답한다. 네 가지 질문을 하나로 뭉뚱그리면 그중 하나는 반드시 구멍이 난다.
권한 게이트를 설계할 때 점검할 것
직접 비슷한 걸 설계하거나 리뷰할 일이 생기면, 아래 표로 층마다 뭘 확인해야 하는지 정리해뒀다.
| 검사 층 | 답하는 질문 | 놓치기 쉬운 지점 |
|---|---|---|
| 규칙 기반 1차 판정 | 이 도구를 써도 되는가 | 규칙이 실행 중 갱신되는 상태라는 점, 대화형/비대화형 맥락 차이 |
| 명령 내용 2차 검사 | 이번에 하려는 일이 실제로 안전한가 | 도구 이름만 보고 내용(옵션·인자)은 안 보는 경우 |
| 경로 검증 | 접근하려는 대상이 허용 범위 안인가 | 심볼릭 링크로 표면 경로와 실제 대상이 달라지는 우회 |
| 승인자 라우팅 | 확인질문을 누구에게 보낼 것인가 | 위임 구조에서 요청 주체와 승인 주체를 같게 취급하는 실수 |
| 실행 격리 | 허용된 실행이 어디까지 손댈 수 있는가 | 판정 통과 후 격리 경계를 다시 확인하지 않는 설계 |
| 감사 로그 | 누가 언제 무엇을 승인했는가 | 실시간 확인만 하고 사후 추적 기록을 안 남기는 경우 |
이 표를 체크리스트로 바꾸면 이렇다.
- 도구 이름 단위 허용 목록과 명령 내용 단위 위험 분류를 같은 것으로 착각하지 않았는가.
- 비대화형(자동 실행) 모드에서 확인질문이 뜨는 명령이 남아 있지 않은가.
- 파일 경로 검증이 심볼릭 링크의 실제 대상까지 따라가는가.
- 위임 구조에서 하위 작업자가 스스로 승인까지 끝내는 경로가 없는가.
- 실행 허용과 실행 격리 범위를 같은 판정 한 번으로 퉁치지 않았는가.
- 확인질문의 요청자·승인자·시각이 로그로 남는가.
이 원칙은 코딩 에이전트에만 해당하지 않는다. 결제를 대신 처리하거나 이메일을 대신 보내는 에이전트를 설계할 때도 똑같은 질문이 반복된다. AI 에이전트에게 일을 맡기기 전에 계약을 정하는 법도 결국 같은 뿌리에서 나온 이야기다. 무엇을 자동으로 허용할지, 무엇을 절대 허용하지 않을지, 애매한 영역은 누구에게 물어볼지를 미리 정해두는 것. 권한 게이트는 그 결정을 코드로 옮겨놓은 것뿐이다.
자주 묻는 질문
허용 목록에 있는 도구라면 어떤 옵션을 붙여도 안전한가?
아니다. Claude Code의 Bash 규칙은 정확한 명령이나 와일드카드 패턴으로 범위를 좁힐 수 있고, 복합 명령은 하위 명령별로 판정한다. 도구 이름 전체를 허용하는 규칙은 옵션과 인자까지 넓게 열 수 있으므로 실제 실행 범위에 맞는 구체적인 패턴을 써야 한다.
비대화형 실행에서 확인질문이 나오면 승인을 기다리면 되는가?
아니다. claude -p 비대화형 실행에서는 허용되지 않은 도구 호출이 발생하면 실행이 중단된다. --allowedTools, allow 규칙, 권한 모드로 필요한 범위를 미리 승인하되 작업에 필요하지 않은 도구까지 넓게 열지 않아야 한다.
허용된 폴더 안의 경로라면 심볼릭 링크도 안전한가?
아니다. Claude Code의 allow 규칙은 링크 경로와 실제 대상이 모두 일치해야 통과하고, deny 규칙은 둘 중 하나만 일치해도 차단한다. 허용 폴더 안의 링크가 범위 밖을 가리키면 자동 허용되지 않는다.
명령 실행이 허용되면 파일 시스템 전체에 접근하게 해도 되는가?
실행 허용은 해도 되는지를 정할 뿐, 어디까지 손댈 수 있는지는 정하지 않는다. 판정 통과 뒤에도 샌드박스로 파일 시스템과 네트워크 접근을 작업에 필요한 최소 범위로 제한해야 한다.
아직 댓글이 없습니다.