Claude Code의 deny만으로는 git push를 완전히 막을 수 없는 이유
요약
AI 에이전트 Claude Code가 개발 과정에서 git push와 같은 위험한 작업을 수행하는 것을 방지하기 위한 방법을 분석합니다. 단순히 `settings.json`의 deny 규칙으로는 우회할 수 있는 허점이 존재하며, 이를 막기 위해 PreToolUse hook을 활용하여 명령어 실행 직전에 검사하고 중단시키는 것이 가장 효과적인 해결책임을 제시합니다.
핵심 포인트
- deny 설정은 명령어 문자열과 대조되므로 다양한 방식으로 우회 가능성이 높습니다.
- Claude Code는 `git -C` 등 다른 디렉토리의 git을 조작하는 경우를 막기 어렵습니다.
- PreToolUse hook을 사용하면 도구 사용 직전에 명령어를 읽고 종료 코드 2로 강제 중단시킬 수 있습니다.
서론
저는 소규모 사업자를 위한 업무 소프트웨어를 혼자 개발하고 있습니다. 개발은 AI 에이전트(Claude Code)에게 맡기고 있습니다. 돈이나 개인 정보를 다루는 소프트웨어이기 때문에, AI가 처리할 수 있는 범위는 **커밋(commit)**까지만으로 정했습니다 (현재로서는). 푸시(push)와 머지(merge)는 사람이 직접 합니다.
처음에는 'CLAUDE.md에 작성하고, settings.json에서 금지시키면 충분할 것'이라고 생각했습니다. 하지만 금지 설정에는 허점이 있었습니다. 공식 문서에도 나와 있습니다. 이 글에서는 다음 세 가지를 정리합니다.
- deny 설정을 통해 어디까지 막을 수 있는지
- 허점을 PreToolUse hook으로 막는 방법
- 그럼에도 남아있는 구멍과 제가 선택한 해결책
작성된 내용은 2026년 10월의 Claude Code 공식 문서와 제가 직접 확인한 내용을 바탕으로 합니다. Claude Code는 자주 변경되므로, 사용하기 전에 가장 최신 문서를 확인해 주십시오.
지시 파일은 강제가 아니다
CLAUDE.md에 'push 하지 말라'고 적는 것에는 의미가 있습니다. 하지만 이것은 Claude가 하려 하는 행동을 유도할 뿐입니다. 공식 문서에도, 지시 파일은 Claude Code가 허용하는 범위를 변경하지 않는다고 쓰여 있습니다.
실제로 막는 것은 Claude Code 자체의 메커니즘(permission rule, permission mode, hook)입니다.
처음 시도한 것: settings.json의 deny
.claude/settings.json에 AI가 수행하지 않았으면 하는 작업을 나열했습니다.
{
"permissions": {
"deny": [
...
Claude Code는 &&, ||, ;, |, & 같은 기호와 줄바꿈을 이해합니다. 따라서 cd repo && git push처럼 연결된 명령이라도, 분리된 각각의 부분이 검사됩니다. $( ) 내부나 for 내부의 명령에도 deny가 적용됩니다.
여기까지는 기대했던 대로였습니다.
허점: 같은 작업을 다른 방식으로 작성하면 막을 수 없다
문제는 rule이 명령어 문자열과 대조된다는 점입니다. 공식 문서에는 Bash(git push *)를 deny 해도, 다음 방식과는 일치하지 않는다고 표로 나와 있습니다.
git -C . push origin main
git -c push.default=current push origin main
git 'push' origin main
curl의 경우도 /usr/bin/curl ...나 sh -c 'curl ...'에는 일치하지 않는다고 합니다. 문서의 결론은, deny rule이 Claude가 평소에 작성하는 형태를 막는 것이지, 그 프로그램 주변의 안전 경계 자체를 막는 것은 아니라는 것입니다.
AI가 다른 디렉토리의 git을 조작하기 위해 git -C를 사용하는 경우는 흔합니다. 그래서 금지를 우회해 버리는 것은 곤란합니다.
대책: PreToolUse hook으로 명령어를 읽고 막기
Claude Code의 hook을 사용하면, Bash와 같은 도구를 사용하기 직전에 원하는 스크립트를 실행할 수 있습니다. PreToolUse hook에는 앞으로 실행될 명령어 전체가 stdin의 JSON(tool_input.command)으로 전달됩니다. hook이 종료 코드 2를 반환하면, 해당 명령어는 중단됩니다. allow rule이나 permission mode보다 이것이 먼저 작동합니다.
그래서 명령어를 단어 단위로 나누어 'git의 어떤 작업인지'를 식별하는 hook을 작성했습니다.
#!/usr/bin/env python3
"""Claude Code의 PreToolUse hook: git의 위험한 작업을, 어떻게 쓰여지든 막는다(기사 목적의 작은 예시).
stdin으로 받는 JSON의 tool_input.command를 읽어, 멈출 때는 종료 코드 2를 반환한다.
...
수행하는 작업은 다음과 같습니다:
- 명령어를
;,&&,|또는 줄바꿈을 기준으로 분리하기- - 앞에 붙는
VAR=값,env,timeout 10
등을 건너뛰기 — 가장 앞에 git이 있다면,
-C
또는 -c
와 그 뒤의 값을 건너뛰고 서브커맨드를 보는 것(-c alias.p=push로 만든 다른 이름도 원래 이름으로 되돌리기) -
bash -c "..."
또는 $( ) 안의 내용은 다시 1부터 읽는다. 따옴표가 닫히지 않아 읽을 수 없을 때는, git이라는 글자가 있으면 중단한다.
중단된 것은 stderr에 기록됩니다. Claude는 그것을 읽고 사람에게 요청하거나 다른 방식으로 전환합니다.
설정은 다음과 같습니다.
{
"hooks": {
"PreToolUse": [
...
"onFailure": "block"
이것을 쓰지 않으면, hook이 작동하지 않거나 중간에 실패하거나 시간 초과가 되는 실패 시에도 명령은 그대로 통과합니다. script의 경로를 하나만 잘못 적어도 전부 무시되는 것입니다. "block"으로 설정해 두면, 실패했을 때도 명령을 중단시킵니다 (Claude Code v2.1.295부터. 공식 문서에서 가져옴).
Windows에서의 주의사항
Windows에서는 hook의 command가 Git Bash로 작동합니다 (Git Bash가 없으면 PowerShell). PowerShell용으로 작성하면 동작하지 않습니다. 또한 Git Bash에서 본 python이 Microsoft Store를 여는 임시 버전인 경우가 있습니다. 가상 환경의 python은 절대 경로로 적는 것이 확실합니다.
정말로 멈추는지 확인하기
hook은 단순히 작성만 한다고 해서 멈출 것이라는 보장이 없습니다. 저는 세 가지 방법으로 확인했습니다.
1. 막아야 하는 형태와 통과시켜야 하는 형태를 모두 테스트에 포함시키기
MUST_BLOCK = [
"git push origin main",
"git -C . push origin main",
...
통과시켜야 하는 형태도 함께 넣는 것은 너무 많이 막지 않았는지 찾기 위함입니다. 커밋 메시지에 "push"라고 적었다는 이유만으로 멈춘다면, AI의 작업이 진행되지 않습니다. 이 예시에서는 막을 형태 22개와 통과시킬 형태 7개가 모두 기대대로 작동했습니다.
2. 일부러 고장 내서 테스트가 실패하는 것을 보기
hook의 -C나 -c를 건너뛰는 처리를 일부러 빼고 테스트를 돌렸습니다. git -C . push 같은 5가지 형태가 "멈추지 않았다"고 보고되었고, 이를 통해 테스트가 허점을 찾을 수 있다는 것을 확인한 후 원래대로 되돌렸습니다.
3. 피해가 없는 작업으로 시도하기
아무것도 지우지 않는 테스트 실행 git -C . clean -n이나 bash -c "git clean -n" 또는 git -c alias.zz=clean zz -n 모두 hook이 막았습니다.
그럼에도 남아있는 허점
이렇게 해도, hook은 강력한 경계는 아닙니다. 제가 환경에서 확인해 보고 남는다고 판단한 것들을 나열합니다.
저장된 인증 정보는 AI의 프로세스로부터도 사용될 수 있습니다. 이는 셸(shell)에서도 python의 subprocess를 통해서도, 확인 화면 없이 사용할 수 있습니다. -
hook 설정은 AI 자신이 수정할 수 있습니다. .claude/settings.json 파일도 hook 스크립트는 리포지토리 안의 단순한 파일입니다. -
최소 예시에는 읽을 수 없는 형태가 존재합니다. $( )의 중첩, 리포지토리 설정에 적힌 alias, PowerShell 구문 등이 있습니다.
더 강력하게 보호하려면
허점의 대부분은 "AI가 사람과 같은 권한으로 작동하고 있다"는 것에서 기인합니다. 가장 좋은 방법은 권한 자체를 분리하는 것입니다.
AI만 사용하는 OS 계정으로 실행한다. 그 계정에 인증 정보를 두지 않으면 걱정할 필요가 없습니다. -
GitHub 측에서 브랜치 보호(branch protection)를 사용한다. 서버 측에서 직접적인 push나 merge를 막을 수 있습니다. 다만 private 리포지토리의 경우, 브랜치 보호 기능을 쓰려면 유료 플랜이 필요합니다.
저는 AI만 사용하는 OS 계정을 선택했습니다. 계정을 분리하면 인증 정보를 어디에 둘지나 SSH 작업을 할 때 등 별도의 번거로움이 생깁니다. 그 이야기는 다음 글에서 쓸 생각입니다.
요약
| 메커니즘 | 막을 수 있는 것 | 막을 수 없는 것 |
|---|---|---|
CLAUDE.md의 지시 | 요청하는 것만 가능 | 강제하지 않음 |
settings.json의 deny | 특정 문자로 시작하는 명령어(git push …) | 동일한 작업을 다른 방식으로 작성하는 방법(git -C . push) |
| PreToolUse hook | 작성 방식을 바꾼 많은 명령어 | 스크립트 내부의 git, 저장된 인증 정보, hook 수정 |
| AI 전용 계정 | 권한 그 자체 | (설정에 노력이 필요함) |
어느 하나만으로는 안전해질 수 없습니다. 여러 개를 겹쳐서, 각각이 무엇을 막지 못하는지 알고 있는 것이 중요하다고 생각합니다.
참고
토론

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