AI Coding Agent에 권한 제어가 필요한 이유: JSON Tool Call에서 Shell, Git, 파일 시스템 권한 모델까지 (2026)
보안 글은 공격면을 이미 썼다. 이 글은 권한 모델만 쓴다: 도구 호출은 JSON이고 실행권은 호스트에 있다. Shell, Git, 파일 시스템은 세 문이다. 허용을 누르는 것은 샌드박스가 닫힌 것이 아니다.
읽기 전 한눈
| 문 | 무엇을 잠그나 | 누가 실행하나 |
|---|---|---|
| Shell | 명령 문자열, 복합 명령 | allow / ask / deny. 샌드박스는 별개 |
| Git | status, diff, commit, push. hooks / config | 작업 영역 쓰기 ≠ 저장소 메타 쓰기 |
| 파일 시스템 | src, .env, 작업 영역 밖 경로 | 도구 규칙 + OS 샌드박스 |
| 네트워크 | 도메인, outbound | 기본은 끔. 목록을 따로 연다 |
정책은 호스트가 평가한다. 모델은 JSON을 채운다. 문을 옮기지 않는다.
권한 제어가 필요한 이유는 실행권이 모델에 없기 때문이다
Coding Agent가 도구를 부를 때 모델은 JSON만 낸다. 이름과 인자. git status를 돌리고 src/orders/A-1042.json을 고치는 것은 호스트다. Schema는 모양을 잠근다. 권한 정책은 모양이 통과한 뒤 실행해도 되는지를 잠근다. 둘째 문이 없으면 첫째는 예의일 뿐이다.
- Schema: 인자를 잠근다. 명령은 문자열. 경로는 정한 접두사 아래.
- 권한 정책: allow / ask / deny. 공식 순서는 deny, ask, allow. 좁은 allow는 넓은 deny를 뚫지 못한다.
- 샌드박스: OS 층. 프롬프트 주입이 모델 판단을 비껴도 프로세스는 작업 영역을 나가지 못한다. 승인은 샌드박스를 여는 것이 아니다.
도구 JSON을 채우는 법은 AI Coding Agent와 JSON 도구 호출. 공격면을 모으는 법은 Agent 보안 위기와 JSON 검증. Schema가 문인 이유는 AI에 JSON Schema가 필요한 이유. 이 글은 그 셋을 반복하지 않는다. Shell / Git / 파일을 객체로 얹을 뿐이다.
권한 객체: allow, ask, deny
Claude Code는 규칙을 settings.json의 permissions에 쓴다. 평가하는 것은 호스트이지 모델이 아니다. CLAUDE.md는 시도만 바꾼다. 허용은 바꾸지 못한다. 가상 저장소. 실제 비밀은 붙이지 마라:
{
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git diff *)",
"Bash(npm test *)",
"Read(./src/**)",
"Edit(./src/**)"
],
"ask": [
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)",
"Read(./.env)",
"Edit(./.git/hooks/**)",
"Edit(./.git/config)"
]
}
}
맨 도구 이름(Bash) deny는 도구를 문맥에서 뺀다. 괄호 규칙은 맞는 호출만 막고 도구는 남긴다. 복합 명령은 하위 명령마다 맞춘다. git status를 허용해도 그 뒤 다른 명령은 허용하지 않는다. Codex는 권한 프로필(:workspace / :read-only)을 쓰며 옛 sandbox_mode와 섞지 않는다. Cursor는 permissions.json 접두 목록과 sandbox.json이다.
첫 문: Shell
모델이 넘기는 것은 명령 문자열이다. 호스트는 정책에 맞춘 뒤 실행한다. 읽기 조회는 대개 통과한다:
{
"type": "tool_use",
"id": "call_7f2a",
"name": "Bash",
"input": {
"command": "git status"
}
}
같은 정책에서 원격 push는 deny다. 객체 모양은 맞다. 실행권은 호스트에 남는다. Cursor 터미널 목록은 접두 일치다. git은 git status를 덮는다. 공식 문서도 Run Mode가 최선을 다하는 가드이지 단단한 경계가 아니라고 쓴다. 단단한 경계에는 샌드박스를 켠다.
{
"type": "tool_use",
"id": "call_8a01",
"name": "Bash",
"input": {
"command": "git push origin main"
}
}
둘째 문: Git은 보통 쓰기가 아니다
작업 영역을 쓸 수 있다고 .git을 쓸 수 있는 것은 아니다. Codex는 쓰기 루트 아래에서 .git, .agents, .codex를 기본으로 읽기 전용으로 둔다. .git이 gitdir: 포인터면 해석된 경로도 지킨다. Claude Code 샌드박스는 worktree가 공유 .git에 commit에 필요한 쓰기를 하게 하고 hooks/와 config는 거절한다. Cursor도 .git/hooks와 .git/config를 지킨다. 막힌 push는 객체로 남겨야 한다:
{
"tool": "Bash",
"command": "git push origin main",
"eval_order": ["deny", "ask", "allow"],
"matched": "Bash(git push *)",
"rule": "deny",
"sandbox": "workspace-write",
"execute": false
}
실패면 맞은 규칙을 적은 tool_result를 되돌린다. 원격을 조용히 건드리지 마라. commit은 ask. 사람이 diff를 본 뒤 통과시킨다. push는 이 세션이 게시라고 명시되지 않으면 deny.
{
"type": "tool_result",
"tool_use_id": "call_8a01",
"is_error": true,
"content": "denied by policy: Bash(git push *)"
}
셋째 문: 파일 시스템은 두 층
한 층은 도구 규칙. 한 층은 OS 샌드박스. 공식이 나누는 이유는 막는 호출이 다르기 때문이다:
- 도구 규칙: Claude의 Read / Edit deny는 내장 파일 도구와
cat/head처럼 알아본 명령을 덮는다. 언어 스크립트가 스스로 파일을 여는 호출은 이 층 밖이다. - OS 샌드박스: 프로세스 경계. 기본 쓰기는 작업 영역과 세션 임시 디렉터리. 바깥 경로는 명시. 네트워크는 기본으로 꺼진다.
- 자격 파일:
.env를 deny에 넣는다. 비밀을 읽지 말라는 문장에만 기대지 마라.
MCP 도구는 다른 표다. 도구마다 문이 필요하다. 프로토콜 대비는 MCP Tool Schema vs Function Calling. 이 글은 Coding Agent가 MCP를 어떻게 부르는지는 쓰지 않는다. 연결된 도구가 권한 객체에 이름을 갖기만 요구한다.
권한을 객체로 담는 다섯 걸음
조심해서 실행하라는 문장에서 시작하지 마라. 문을 쓴다:
- 먼저 deny를 쓴다. push, hooks, config, .env, 작업 영역 밖 경로를 한 번에 적는다.
- 읽기 명령을 allow에 넣는다. status, diff, test. 복합은 하위 명령마다 한 줄.
- 쓰기와 commit은 ask. 사람이 경로와 diff를 본다. 편집 승인은 세션과 함께 끝나고 영구 규칙이 되지 않는다.
- OS 샌드박스를 켠다. Codex는
:workspace. 함부로:danger-full-access를 쓰지 마라. Cursor 기본을 Run Everything으로 두지 마라. Claude의bypassPermissions는 격리 컨테이너에 남긴다. - 정책 자체를 JSON으로 본다. allow와 deny를 펼친다. 공유 규칙은 버전 관리. 로컬 완화는 local 파일에 남긴다.
Schema를 문으로 쓰는 이야기는 Agents API + JSON Schema. 이 글이 요구하는 것은 Coding Agent의 문이 모두 객체이고 호스트가 평가한다는 것이다.
권한 이탈을 되짚으려면 JSON 두 줄을 펼친다
문을 넘긴 대부분은 규칙이 어긋난 것이다. 모델이 갑자기 거칠어진 것이 아니다.
- 정책과 그
tool_use를 포맷하고 deny가 맞았는지 allow가 샜는지를 본다. - JSONPath 추출로
$.permissions.deny와$.input.command를 뽑는다. - 통과한 호출과 거절된 호출을 JSON Diff. 규칙을 먼저 고치고 프롬프트는 나중.
- Git 메타 규칙과 src 쓰기는 따로 본다.
A-1042.json을 고칠 수 있다고 hooks를 고칠 수 있는 것은 아니다. - 정책 파일, 저장소 경로, 세션 로그를 공개 포맷터에 붙이지 마라. JSONSort는 브라우저에서 로컬로 돈다.
두 궤적이 규칙으로 다시 세워져야 권한을 모은 것이다. 아니면 허용을 눌렀다를 모은 것이다.
흔한 함정
- Schema를 권한으로 본다. Schema는 모양을 잠근다. 모양이 맞는
git push에도 deny가 필요하다. - allow만 쓴다. 공식 순서는 deny가 먼저다. deny가 없으면 목록은 승인만 빠르게 한다.
- 작업 영역 쓰기를 Git 쓰기로 본다. hooks와 config는 다른 문이다. 공식 기본이 지킨다.
- 도구 deny만 쓰고 샌드박스를 끄지 않는다. 스크립트가 스스로 파일을 여는 호출은 도구 규칙 밖이다. 프로세스 경계가 필요하다.
- 노트북에서 bypass / full-access / Run Everything을 켠다. 공식은 격리 환경용이다. 일상 세션은 workspace + ask.
- Codex 옛것과 새것을 섞는다.
sandbox_mode가 있으면 권한 프로필은 적용되지 않는다. 하나를 고른다.
FAQ
허용하고 다시 묻지 않음이 영구 승인인가?
아니다. Claude Code에서 Bash의 영구 승인은 저장소 루트 settings.local.json에 떨어지고, 파일 편집 승인은 세션과 함께 끝난다. Cursor 목록은 permissions.json. 쓰인 객체를 본다. 버튼 문구를 믿지 마라.
샌드박스가 켜져 있으면 deny는 필요 없나?
필요하다. 샌드박스는 프로세스가 작업 영역을 나가는 것을 막는다. deny는 그 안에서 돌면 안 되는 명령, 예를 들어 push를 막는다. 잠그는 문이 다르다.
Git을 따로 두는 이유는?
저장소 메타는 보통 파일이 아니다. src를 고칠 수 있다고 hooks와 config를 고칠 수 있는 것은 아니다. push는 outbound이지 디스크 쓰기가 아니다. 공식 제품은 이를 작업 영역 쓰기에서 빼낸다.
읽기 전용으로 충분한가?
코드를 보기에는 충분하다. A-1042를 바꾸기에는 부족하다. Codex :read-only, Claude plan, Cursor 읽기 샌드박스는 탐색용이지 게시용이 아니다.
정책을 Git에 넣어도 되나?
된다. 넣어야 한다. 공유 deny / allow는 버전 관리. 로컬 완화는 local 파일에 남기고 커밋하지 않는다. bypass와 auto를 끄는 것은 managed 설정이다.
결론
Coding Agent에 권한 제어가 필요한 이유는 모델이 JSON만 채우기 때문이다. Shell, Git, 파일 시스템은 세 문이다. Schema를 통과한 뒤에도 호스트가 다시 평가한다.
git push가 어느 deny에 걸리는지를 먼저 쓴다. 경로를 가리킬 수 있으면 정책은 검증 가능하다. 모델은 바뀐다. 문을 먼저 안정시킨다.
더 읽기
- AI Coding Agent는 JSON으로 도구를 어떻게 부르나 Claude Code와 GPT Agent의 도구 JSON
- Agent 보안 위기와 JSON 검증 공격면을 모으는 글. 권한 객체가 아님
- Claude 보안 사건과 도구 JSON 한 사건에서 인자가 어떻게 흘렀나
- Agents API + JSON Schema 실전 안정, 검증, 실행의 세 문
- AI에 JSON Schema가 필요한 이유 Schema / Function Calling / Structured Output
- JSONSort 로컬 도구상자 포맷, 검증, Diff
- 모든 글 JSON과 AI 개발 메모
변경 기록: 최초 게시