-
AI 코딩 에이전트의 권한 관리 — 화이트리스트에서 블랙리스트로 전환한 이유IT 2026. 4. 22. 23:00
191개 허용 목록을 관리하다 지쳤다
AI 코딩 에이전트에게 터미널 명령 실행 권한을 주는 방식은 크게 두 가지입니다.
- 화이트리스트: 허용한 명령만 실행. 나머지는 사용자에게 물어봄.
- 블랙리스트: 전부 실행. 위험한 것만 차단.
처음에는 당연히 화이트리스트를 선택했습니다. 안전하니까요.
git *,python3 *,ls *같은 패턴을 하나씩 추가했습니다.문제는 시간이 지나면서 드러났습니다.
누적되는 일회성 항목
에이전트가 새로운 명령을 실행하려 할 때마다 "이 명령을 허용하시겠습니까?" 프롬프트가 뜹니다. 허용하면 그 정확한 명령 문자열이 allow 목록에 추가됩니다. 그 결과:
kill 44489— 특정 PID를 죽이려 했던 일회성 명령cp /home/user/project/very/specific/path/file.md /tmp/backup/— 한 번 쓰고 다시는 안 쓸 경로CUDA_VISIBLE_DEVICES=0 PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync python3 -c "..."— 환경변수가 붙은 긴 명령
6개월 사용 후 allow 목록은 360개 항목으로 불어났습니다. 이 중 절반 이상이 다시는 실행되지 않을 일회성 명령이었습니다.
복합 명령의 함정
더 큰 문제는 복합 명령이었습니다. 화이트리스트 매칭은 명령 문자열의 시작 부분만 봅니다.
# "git"으로 시작 → Bash(git *) 매칭 → Allow git add . && git commit -m "fix" # "CUDA_VISIBLE_DEVICES="로 시작 → 매칭되는 패턴 없음 → Ask! CUDA_VISIBLE_DEVICES=0 python3 train.py환경변수로 시작하는 명령, 서브셸로 감싼 명령, 셸 제어 구문(
if,[,test)으로 시작하는 명령은 allow에 패턴이 없으면 매번 물어봅니다. 그래서CUDA_VISIBLE_DEVICES=*,OLLAMA_HOST=*같은 환경변수 접두어도 allow에 추가해야 했습니다.이쯤 되면 의문이 듭니다: 화이트리스트를 유지하는 비용이 그것이 막아주는 위험보다 큰 것 아닌가?
발상의 전환 — 전부 허용하되, 위험한 것만 막자
결론부터 말하면, allow 목록을 191개에서 1개로 줄였습니다.
{ "permissions": { "allow": ["Bash(*)"], "deny": [ "Bash(rm -rf *)", "Bash(sudo rm -rf *)", "Bash(git reset --hard*)", "Bash(git push --force*)", "Bash(chmod -R 777*)", ... ] } }Bash(*)는 "모든 Bash 명령을 허용한다"는 뜻입니다. 대신 deny 목록에 절대 실행되면 안 되는 9개 패턴을 넣었습니다.왜 이런 선택을 했는가
핵심 판단은 이것이었습니다:
AI 에이전트가 실행하는 명령의 99%는 안전하다. 위험한 1%를 잡기 위해 99%에 매번 허가를 받는 것은 비효율적이다.
이것은 보안 분야에서 말하는 기본 허용(default allow) vs 기본 차단(default deny) 패러다임의 선택입니다. 기업 서버라면 당연히 기본 차단이 맞지만, 개인 개발 환경에서 AI 에이전트와 빠르게 협업하려면 기본 허용이 더 실용적입니다.
다만 기본 허용을 선택하려면 차단 메커니즘이 충분히 견고해야 합니다. 그래서 이중 방어를 구축했습니다.
이중 방어 — settings deny + PreToolUse Hook
블랙리스트의 약점은 패턴 우회입니다. deny 목록에
rm -rf *를 넣어도, 에이전트가rm -r -f /home처럼 플래그 순서를 바꾸면 빠져나갈 수 있습니다. 문자열 매칭은 정확한 형태만 잡기 때문입니다.그래서 두 번째 방어선으로 PreToolUse Hook을 추가했습니다.
PreToolUse Hook이란
Claude Code에는 에이전트가 도구를 실행하기 직전에 외부 스크립트를 실행하는 Hook 시스템이 있습니다. PreToolUse Hook에 등록한 스크립트가 exit code 2를 반환하면, 해당 도구 실행이 차단됩니다.
# 실행 흐름 에이전트가 Bash 명령 실행 요청 ↓ [PreToolUse Hook] dispatcher_pre_bash.py ↓ 1단계: safety.check_safety(cmd) ↓ 정규식 매칭 → 차단 (exit 2) ↓ 매칭 안 됨 → 통과 2단계: 파이프라인별 검증 (git commit 등) ↓ 실제 명령 실행정규식 기반 차단 — 변형도 잡는다
settings의 deny 목록이 문자열 매칭이라면, safety Hook은 정규식입니다. 예를 들어:
# settings deny: "Bash(rm -rf *)" — 정확히 "rm -rf"로 시작해야 매칭 rm -rf / → 차단 ✓ rm -r -f /home → 통과 ✗ (형태가 다름) # safety.py 정규식: rm\s+.*(-rf|-fr|-r.*-f|-f.*-r)\s+(/|~|/home) rm -rf / → 차단 ✓ rm -r -f /home → 차단 ✓ (플래그 순서 무관) rm -fr ~/ → 차단 ✓ (-fr도 매칭)safety.py가 차단하는 8가지 패턴은 다음과 같습니다:
위험 행동 왜 차단하는가 rm -rf+ 루트/홈/넓은 경로시스템 전체 또는 홈 디렉토리 삭제. 복구 불가 sudo rm -rf루트 권한 대량 삭제 dd if=... of=/dev/블록 디바이스에 직접 쓰기. 디스크 파괴 mkfs,fdisk,parted파일시스템/파티션 조작 chmod -R 777+ 넓은 경로보안 전면 해제 git reset --hard커밋되지 않은 변경 전체 소실 git push --force원격 저장소 히스토리 파괴 git clean -f추적되지 않는 파일 일괄 삭제 이중 방어가 필요한 이유
한쪽만 있으면 안 되는 이유가 있습니다:
방어 수단 강점 약점 settings deny 에이전트가 도구 호출 자체를 하지 않음 (하네스 레벨) 정확한 문자열만 매칭, 변형 우회 가능 safety.py Hook 정규식으로 변형 패턴도 포착 Hook 스크립트에 버그가 있으면 무방비 settings deny는 빠르고 확실한 1차 방어, safety.py는 유연한 2차 방어입니다. 둘 다 뚫려야 위험 명령이 실행됩니다.
이 실험의 장점과 단점
블랙리스트 전환 후 당분간 이 방식으로 실험을 진행합니다. 예상되는 장단점을 정리합니다.
장점
- 작업 흐름이 끊기지 않는다 — "이 명령을 허용하시겠습니까?"가 사라졌습니다. 에이전트가 환경변수를 붙이든, 파이프를 연결하든, 새로운 CLI 도구를 호출하든 바로 실행됩니다.
- 유지보수 비용이 0에 가깝다 — 새로운 도구를 설치해도 allow 목록을 수정할 필요가 없습니다. deny 패턴만 관리하면 됩니다.
- 설정 파일이 읽기 쉬워졌다 — 191줄의 allow 목록 대신 1줄. deny 9줄. 전체 정책이 한 화면에 들어옵니다.
- 정책의 의도가 명확하다 — "이 9가지만 하지 마라"는 "이 191가지를 해도 된다"보다 이해하기 쉽습니다.
단점과 리스크
- deny 패턴에 빈틈이 있으면 위험하다 — 화이트리스트는 빈틈이 있어도 "알려지지 않은 명령은 물어보기"로 안전하지만, 블랙리스트는 "알려지지 않은 위험은 그냥 실행"입니다. 정규식 패턴의 완성도가 곧 안전성입니다.
- 에이전트가 의도치 않은 부작용을 일으킬 수 있다 — 파일 삭제(
rm)를 매번 확인하지 않으므로, 에이전트가 잘못 판단해서 필요한 파일을 지울 가능성이 있습니다. 물론rm -rf는 차단되지만, 단일 파일rm은 통과합니다. - 감사 추적이 약하다 — 모든 게 자동으로 실행되면 "에이전트가 뭘 했는지" 추적이 어려워집니다. 현재는 Claude Code의 세션 로그에 의존하고 있는데, 별도 감사 시스템이 필요할 수 있습니다.
- 새로운 위험 패턴을 사후에 발견할 수 있다 — 현재 8개 정규식이 모든 위험을 커버하지 못할 수 있습니다. 사고가 나야 패턴을 추가하게 되는 사후 대응 구조입니다.
리스크 완화 전략
이 단점들을 감수할 수 있는 이유는 다음과 같습니다:
- 개인 환경이다 — 다른 사용자에게 영향을 주지 않습니다. 실수의 범위가 자기 자신으로 한정됩니다.
- Git이 안전망이다 — 코드 변경은 모두 Git으로 추적됩니다.
git reset --hard와push --force를 차단했으므로, 커밋된 코드는 항상 복구할 수 있습니다. - 정책 개선 루프를 운영한다 — "사고가 나면 패턴 추가"가 아니라, 주기적으로 세션 로그를 검토하여 위험했던 명령을 식별하고 선제적으로 패턴을 추가합니다.
- 이중 방어가 있다 — settings deny와 safety.py Hook, 두 겹의 차단을 모두 우회하기는 매우 어렵습니다.
HITL Policy의 3단계 — Allow, Ask, Deny
블랙리스트 전환 후에도 3단계 분류 자체는 유지합니다. 구현 방식만 바뀐 것입니다.
등급 기준 구현 방식 Allow 부작용 없거나 쉽게 되돌림 Bash(*)로 전체 허용Ask 되돌리기 비용이 있음 현재는 Allow에 포함 (실험 중) Deny 복구 불가/시스템 파괴적 settings deny + safety.py Hook 판단 기준은 간단합니다: 위험도 = 비가역성 × 영향 범위
- 비가역성 높고 범위 넓음 → Deny (rm -rf /, git push --force)
- 비가역성 높고 범위 좁음 → 현재는 Allow, 사고 시 Deny로 강등
- 비가역성 낮음 → Allow (대부분의 명령)
Ask 등급은 사실상 비워뒀습니다. 이것이 이 실험의 핵심입니다. "물어보는 것" 자체를 최소화하고, 대신 차단 메커니즘의 견고함에 투자했습니다.
교훈 — 통제의 무게중심을 옮기다
이전 글에서 Harness Engineering의 Control Plane을 다뤘습니다. HITL Policy는 그 Control Plane의 핵심 구성 요소입니다.
이번 전환에서 배운 것은, 통제의 무게중심을 "허가"에서 "차단"으로 옮기면 에이전트의 자율성과 안전성을 동시에 높일 수 있다는 것입니다.
- 화이트리스트 시절: 에이전트가 할 수 있는 일을 사람이 하나씩 열어줌 → 안전하지만 느림
- 블랙리스트 전환 후: 에이전트가 할 수 없는 일을 시스템이 자동으로 막음 → 빠르고, 차단이 견고하면 안전
이것은 사이버네틱스 관점에서 피드백 루프의 위치를 바꾼 것입니다. 모든 행동 전에 확인하는 사전 피드백(pre-feedback)에서, 위험 패턴만 감지하는 실시간 감시(real-time monitoring)로 전환한 것이죠.
당분간 이 방식으로 실험을 계속하면서, deny 패턴의 빈틈이 드러나면 보강하고, 불필요한 차단이 있으면 완화할 예정입니다. Harness Engineering의 IMPROVE 루프가 바로 이 과정입니다.
참고 자료
- Anthropic, Effective Harnesses for Long-Running Agents
- Claude Code, Settings & Permissions
- Claude Code, Hooks Reference
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
아키텍처 문서에는 뭘 써야 할까 — AI 에이전트가 읽는 시대의 architecture.md (0) 2026.04.25 Docs-as-Code로 사이드 프로젝트 문서화하기 — 코드 위키 실전 적용기 (1) 2026.04.25 벡터 DB 온디맨드 관리 — Qdrant와 임베딩 서버의 cold start 실측 (0) 2026.04.24 Claude Code settings.json vs settings.local.json — 왜 둘로 나뉘었나 (0) 2026.04.23 AI 에이전트가 뭘 했는지 추적하기 — 경량 감사 로그 구축기 (0) 2026.04.23 코딩 에이전트는 README.md를 읽을까? — 2026년 4월 실측 현황 (0) 2026.04.22 Context Engineering — AI 코딩 에이전트에 맥락을 주입하는 우선순위 체계 (0) 2026.04.22 Claude Code 스킬과 훅 — AI 코딩 도구에 왜 통제 체계가 필요한가 (1) 2026.04.22 텔레그램으로 GitHub 이슈 관리 자동화하기 (1) 2026.04.21 6개월 만에 4세대, 智谱AI GLM 모델 패밀리 완전 정리 (1) 2026.04.20