View

728x90
반응형

 

Claude Code가 rm -rf ./를 실행해 로컬 파일을 전부 날린 사례를 들어본 적이 있는가? 농담이 아니다. 일본 Qiita 개발자 커뮤니티에 정리된 실제 사고만 7건이다. Claude Code 보안 이슈는 툴이 나빠서 발생하는 것이 아니라, 자동 승인을 켜둔 채 세팅을 하지 않아 발생한다. 이 글에서는 실제 발생한 사고 7건과 각각의 방지법, 그리고 지금 당장 settings.json에 추가할 deny 리스트까지 모두 정리한다. 3분이면 끝나니 끝까지 읽고 지금 바로 세팅하기 바란다.

 

사고 7개 요약부터 살펴본다

 

아래에서 하나씩 다룰 예정이지만, 먼저 전체 구조부터 보면 이해가 쉽다.

 

사고 발생 내용 피해 규모 방지 키포인트
1 `.env` 깃허브 푸시 수 분 내 API 키 크롤링 `.gitignore` + gitleaks
2 프로덕션 DB `DROP TABLE` 3일치 데이터 증발 SQL deny 패턴
3 `rm -rf ./` 전체 삭제 로컬 프로젝트 전부 recursive delete 차단
4 API 키 로그 평문 노출 서브에이전트 로그 유출 환경변수 참조 강제
5 무한 재시도로 API 호출 3000회 $200 요금 폭탄 max retry 3회
6 `git push --force`로 동료 커밋 3개 삭제 팀원 하루치 작업 증발 `--force-with-lease`만 허용
7 Owner 권한 서비스 계정 오남용 Cloud SQL 요금 폭탄 IAM 최소권한

 

이 7건에는 공통점이 있다. 모두 "잠깐 승인 눌러볼까?" 한 순간에 터진 사고다. 다만 한 번만 세팅해두면 평생 발생하지 않는다.

 

사고 1 — .env 파일이 깃허브로 푸시되다

 

가장 흔한 사고다. Claude Code에 "초기 커밋 해줘"를 지시했는데, 에이전트가 순진하게 git add .를 실행해 버린다. .env 파일에 OpenAI 키와 AWS 시크릿이 모두 박혀 있는 상태로 퍼블릭 레포에 올라간다.

 

몇 분 만에 크롤러가 키를 수집한다

 

깃허브 퍼블릭 레포는 실시간으로 크롤링된다. .env 커밋 후 평균 10분 안에 봇이 키를 수집해 간다. 탈취된 AWS 키로 EC2 인스턴스 수십 대가 뜨고 비트코인 채굴이 돌아간다. 다음 날 아침 $3000짜리 청구서가 날아온 실제 사례다.

 

방지: .gitignore + pre-commit hook

 

# .gitignore에 최소한 이거는 박아둠
.env
.env.*
!.env.example
*.pem
*.key
config/secrets.yml

 

다만 .gitignore만으로는 부족하다. 이미 커밋된 파일에는 적용되지 않는다. 따라서 gitleaks 같은 도구로 pre-commit 단계에서 차단해야 한다.

 

# gitleaks 설치 및 pre-commit hook 등록
brew install gitleaks  # 맥
# 또는 scoop install gitleaks  # 윈도우

# .git/hooks/pre-commit 에 추가
#!/bin/sh
gitleaks protect --staged --verbose --redact

 

gitleaks 공식 레포에 가서 readme대로 따라 하면 2분이면 끝난다. 이것 하나만으로도 Claude Code 보안 사고의 절반은 차단된다.

 

 

출처: github.com

 

사고 2 — 프로덕션 DB에 DROP TABLE을 실행하다

 

"안 쓰는 테이블 정리해줘"라는 한 줄이 회사의 3일치 주문 데이터를 날린 사고다. Claude Code는 스테이징 DB에 접속한 줄 알고 작업했지만, 실제로는 프로덕션 커넥션이 .env에 남아 있었다.

 

왜 발생하는가

 

에이전트는 접속 문자열에 prod라고 쓰여 있어도 "괜찮아 보인다"며 그대로 넘어간다. 개발자도 "yolo 모드"로 자동 승인을 켜두면 확인 없이 실행된다. 백업이 없다면 그대로 사라진다.

 

방지: settings.json에 DB 쿼리 패턴 deny

 

Claude Code의 ~/.claude/settings.json에 위험 명령 패턴 deny 리스트를 추가해두면 된다.

 

{
  "permissions": {
    "deny": [
      "Bash(*DROP TABLE*)",
      "Bash(*DROP DATABASE*)",
      "Bash(*TRUNCATE*)",
      "Bash(*DELETE FROM*WHERE*1=1*)",
      "Bash(mysql*--force*)",
      "Bash(psql*-c*DROP*)"
    ],
    "ask": [
      "Bash(*UPDATE*SET*)",
      "Bash(*DELETE FROM*)"
    ]
  }
}

 

deny는 실행 자체를 차단하고, ask는 매번 수동 확인을 띄운다. 프로덕션 DB는 읽기 전용 계정으로 분리하고, 쓰기는 마이그레이션 툴(Flyway, Liquibase)로만 처리하는 것이 원칙이다.

 

사고 3 — rm -rf ./로 빌드 폴더 대신 전체를 삭제하다

 

rm -rf ./dist를 실행해야 했는데 Claude Code가 rm -rf ./를 실행한다. 경로 하나 차이로 프로젝트 루트 전체가 날아간다. 깃에 커밋하지 않은 작업 6시간 분량이 사라졌다.

 

왜 에이전트가 이런 일을 저지르는가?

 

사용자가 "빌드 폴더 지워줘"를 지시했지만, 에이전트 컨텍스트 안에서 pwd가 이미 dist/에 있었다. 그 상태에서 ./를 입력하면 현재 디렉토리 전체가 대상이 된다. 한 글자 차이로 지옥행이다.

 

방지: recursive delete 완전 차단

 

{
  "permissions": {
    "deny": [
      "Bash(rm -rf /*)",
      "Bash(rm -rf ./*)",
      "Bash(rm -rf ~/)",
      "Bash(rm -rf ..)",
      "Bash(find * -delete)",
      "Bash(shred*)"
    ]
  }
}

 

추가로 Claude Code 실행 폴더를 Git 워크트리 안에서만 사용하는 습관을 들이면 안전하다. 최소한 커밋되지 않은 것은 전부 날아간다는 기본기는 반드시 지켜야 한다.

 

사고 4 — API 키가 서브에이전트 로그에 남다

 

"OpenAI 프록시 만들어줘, 키는 sk-proj-abc123..."이라고 프롬프트에 직접 입력한다. Claude Code가 서브에이전트를 호출하면서 프롬프트 내용을 로그 파일에 평문으로 남긴다. ~/.claude/logs/를 뒤져보면 키가 그대로 남아 있다.

 

로그가 다른 서비스로 흘러간다

 

여기서 진짜 무서운 점은, 일부 관측 도구가 에이전트 로그를 자동으로 Datadog이나 Sentry로 전송한다는 것이다. 그러면 서드파티 SaaS 서버에도 키가 퍼진다. 한 번 퍼진 로그는 회수가 불가능하다.

 

방지: 프롬프트에 시크릿 입력 절대 금지

 

# 좋은 예: 환경변수 참조만 함
export OPENAI_API_KEY=sk-proj-xxxx
# 그리고 Claude한테는
# "$OPENAI_API_KEY 환경변수 써서 프록시 만들어줘"

# 나쁜 예: 프롬프트에 키를 직접 입력
# "키는 sk-proj-abc123... 쓰면 됨" ← 이거 절대 금지

 

팀 단위라면 direnv + .envrc로 로컬 환경변수를 관리하고, .envrc 자체를 .gitignore에 추가해야 한다. 그리고 키는 주기적으로 로테이션(90일 권장)하는 것을 잊지 말아야 한다.

 

사고 5 — API 호출 1시간에 3천 번, $200 요금 폭탄

 

에이전트가 외부 API를 호출하는 스크립트를 돌리다가 429 에러를 만났다. 그런데 재시도 로직이 무한 루프였다. 1시간 안에 3000번을 호출했고, Anthropic 요금으로 $200, 거기에 대상 API 서비스에서 IP 블랙리스트까지 당했다.

 

무한 재시도가 돈을 불태운다

 

Claude Code는 기본적으로 "실패하면 재시도" 패턴을 선호한다. 다만 backoff 없이 즉시 재시도하면 비용은 기하급수적으로 늘어난다. 특히 claude-opus 같은 비싼 모델을 사용 중이라면 1시간에 $200은 우습게 나온다.

 

방지: exponential backoff + max retry 3회

 

에이전트에게 스크립트 작성을 요청할 때 반드시 지정한다.

 

import time
import random

def call_with_backoff(func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return func()
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f"재시도 {attempt+1}/{max_retries}, {wait:.1f}초 대기")
            time.sleep(wait)

 

그리고 Anthropic 콘솔에서 월 사용량 알림을 설정해둔다. $50, $100 단위로 슬랙 웹훅이 날아오게 설정하면 폭탄을 맞기 전에 차단할 수 있다.

 

사고 6 — git push --force로 팀원 커밋 3개를 날리다

 

브랜치 히스토리 정리를 지시했다. Claude Code가 git rebase -i 이후 git push --force를 실행한다. 그 사이 동료가 푸시한 커밋 3개가 원격에서 사라진다. 동료는 다음 날 아침 출근해 자신의 작업이 없어진 것을 보고 멘붕에 빠진다.

 

force push는 원격을 덮어쓴다

 

--force는 로컬 상태로 원격을 완전히 덮어쓰기 때문에 중간에 들어온 다른 사람의 커밋이 소리 없이 사라진다. 백업이 없다면 reflog를 뒤져 겨우 복구해야 하는데, 로컬에 없는 커밋은 영영 돌아오지 않는다.

 

방지: --force 거부, --force-with-lease만 허용

 

{
  "permissions": {
    "deny": [
      "Bash(git push --force*)",
      "Bash(git push -f*)",
      "Bash(git push origin * --force)"
    ],
    "allow": [
      "Bash(git push --force-with-lease*)"
    ]
  }
}

 

--force-with-lease는 마지막으로 확인한 원격 상태와 현재 원격 상태가 같을 때만 푸시한다. 즉, 누군가 중간에 푸시했다면 거부된다. 안전한 대안이다. 팀 단위라면 깃허브 레포 설정에서 브랜치 보호 규칙으로 main, develop 쪽 force push를 아예 막아두는 것이 정석이다.

 

사고 7 — Owner 권한 서비스 계정으로 BigQuery까지 건드리다

 

GCP 작업을 맡기려고 Owner 권한 서비스 계정 키를 Claude Code에 전달했다. "Cloud Storage 버킷 정리해줘"를 지시했지만, 에이전트가 컨텍스트를 착각해 BigQuery 쿼리 수백 개를 실행한다. 쿼리당 스캔 비용이 발생해 한 달 요금이 $800 추가됐다.

 

Owner는 모든 권한을 가진다

 

Owner 롤은 거의 모든 GCP 리소스에 접근이 가능하다. 에이전트가 실수로 다른 프로젝트 리소스를 건드렸을 때 이를 막을 IAM 레이어가 존재하지 않는다. 바이브 코딩을 하다 보면 "편하게 다 되게 해주자"라는 마인드로 이런 일이 흔하게 발생한다.

 

방지: 최소권한 + 작업별 서비스 계정 분리

 

# Cloud Storage 전용 서비스 계정 만들기
gcloud iam service-accounts create claude-storage-sa \
  --display-name "Claude Code Storage Only"

# 필요한 역할만 부여
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:claude-storage-sa@PROJECT_ID.iam.gserviceaccount.com" \
  --role="roles/storage.objectAdmin"

# BigQuery 접근 원천 차단 확인
gcloud projects get-iam-policy PROJECT_ID \
  --flatten="bindings[].members" \
  --filter="bindings.members:claude-storage-sa*"

 

작업 영역이 바뀌면 서비스 계정을 교체하는 습관을 들여야 한다. 한국 클라우드인 NCP, KT Cloud도 IAM 기능이 있으므로 같은 원칙을 적용하면 된다.

 

지금 당장 적용해야 할 Claude Code 보안 세팅 3가지

 

사고 7건을 모두 살펴봤는데, 결국 핵심은 세 가지로 수렴한다. 이 섹션만 읽어도 90%는 방지된다.

 

1. settings.json deny 리스트 복붙

 

~/.claude/settings.json (프로젝트별은 .claude/settings.json) 파일이 없다면 생성하고 다음 내용을 추가하라.

 

{
  "permissions": {
    "deny": [
      "Bash(rm -rf /*)",
      "Bash(rm -rf ./*)",
      "Bash(rm -rf ~/)",
      "Bash(git push --force*)",
      "Bash(git push -f *)",
      "Bash(*DROP TABLE*)",
      "Bash(*DROP DATABASE*)",
      "Bash(*TRUNCATE*)",
      "Bash(chmod -R 777*)",
      "Bash(curl*|*sh)",
      "Bash(wget*|*bash)",
      "Write(**/.env)",
      "Write(**/.env.*)",
      "Write(**/*.pem)"
    ],
    "ask": [
      "Bash(git push*)",
      "Bash(npm publish*)",
      "Bash(docker push*)",
      "Bash(terraform apply*)"
    ]
  }
}

 

deny는 무조건 차단하고, ask는 매번 확인을 요구한다. 프로덕션 리소스를 건드리는 명령은 모두 ask로 분리해야 한다.

 

2. pre-commit hook 한 줄 설치

 

# gitleaks 전역 훅 설정
gitleaks install --hook-type pre-commit --global

# 또는 프로젝트 단위로
cd your-project
pre-commit install

 

.pre-commit-config.yaml에 gitleaks를 추가하면 끝이다. 설치하고 나면 .env나 API 키는 절대 커밋되지 않는다.

 

3. 프로덕션 환경변수 분리

 

dev, stage, prod 환경변수 파일을 물리적으로 분리한다.

 

config/
├── .env.dev       # 개발용, 가짜 DB
├── .env.stage     # 스테이징
├── .env.prod      # 프로덕션 (별도 저장소, 평소엔 로컬에 없음)
└── .env.example   # 깃 트래킹용 템플릿

 

Claude Code는 .env.dev만 접근 가능하도록 제한하고, 프로덕션 키는 1Password나 Vault에 보관해 필요할 때만 꺼내 사용한다. 매일 쓰는 키가 아니라는 점을 몸에 새겨야 한다.

 

사고가 발생하면 무엇부터 해야 하는가?

 

예방도 중요하지만, 이미 발생한 경우의 대응법도 빨리 익혀둬야 한다. 순서를 외워두자.

 

  1. API 키 즉시 로테이션: 해당 서비스 콘솔에 들어가 키를 revoke한다. AWS, OpenAI, Anthropic, GCP 모두 콘솔에서 1분이면 끝난다.
  2. 레포에서 히스토리 제거: git filter-repo --path .env --invert-paths를 실행해 커밋 히스토리 자체에서 삭제한다. BFG Repo-Cleaner도 쓸 만하다.
  3. force push로 원격 덮어쓰기: 팀원이 있다면 반드시 공지 후 실행한다. 이때만 --force를 사용해야 한다.
  4. 팀 공유 + 사후 리뷰: 왜 발생했는지 포스트모템을 작성해 팀 위키에 공유한다. 같은 사고가 반복되지 않도록 settings.json 템플릿을 업데이트한다.
  5. 결제 명세 모니터링: 사고 발생 후 48시간 동안은 결제 알림을 바짝 감시한다. 도둑 계정 사용 징후가 보이면 AWS GuardDuty 같은 도구로 IP를 추적한다.

 

Anthropic 공식 Security 문서에서 베스트 프랙티스를 추가로 참고하면 좋다. 다만 공식 문서는 원론 수준이고, 실제 사고 기반 대응은 이런 커뮤니티 글을 참고하는 것이 훨씬 실용적이다.

 

Claude Code vs Cursor vs Copilot 보안 설정 차이

 

다른 AI 코딩 도구를 사용하는 독자를 위해 간단한 비교표를 남긴다.

 

도구 권한 제어 명령 차단 시크릿 스캔
Claude Code `settings.json` deny/ask 정규식 패턴 없음 (외부 도구 필요)
Cursor `.cursorrules` 가이드만 없음 (에이전트 판단에 의존) 없음
GitHub Copilot IDE 레벨 승인 없음 GitHub Secret Scanning

 

Claude Code가 가장 세분화된 권한 제어를 제공한다. Cursor는 가이드 텍스트 수준이라 에이전트가 지킬지 여부가 운에 달려 있다. Copilot은 깃허브 레벨 시크릿 스캐닝이 자동인 대신 쉘 명령 차단은 약하다. 각자 다르므로 사용하는 도구마다 세팅을 반드시 확인해야 한다.

 

정리: Claude Code 보안 체크리스트

 

7건의 사고를 다시 훑으며 마무리한다. 모두 읽었다면 아래 체크리스트를 보고 지금 당장 하나씩 점검하라.

 

  • [ ] .gitignore에 .env, .pem, .key 추가 완료
  • [ ] gitleaks 또는 detect-secrets pre-commit hook 설치 완료
  • [ ] ~/.claude/settings.json에 deny 리스트 추가 완료
  • [ ] 프로덕션 DB는 읽기 전용 계정으로 분리 완료
  • [ ] git push --force 차단, --force-with-lease만 허용
  • [ ] Anthropic 콘솔 월 사용량 알림 설정 완료
  • [ ] 서비스 계정은 Owner가 아닌 작업별 최소권한으로 분리
  • [ ] 프롬프트에 API 키 직접 입력 금지, 환경변수 참조만 사용

 

Claude Code 보안은 어려운 일이 아니다. 5분만 투자해 세팅을 한 번 해두면 7가지 사고가 모두 사전에 차단된다. 나는 이 작업을 모두 마쳤고 이후로 한 번도 사고가 발생하지 않았다. 지금 터미널을 열고 ~/.claude/settings.json 편집부터 시작하라. 나중에 "아, 그때 할걸"이라며 후회하면 이미 늦다.

 

이 글의 사고 사례 7선은 일본 개발자 masa_ClaudeCodeLab의 Qiita 원본 기사를 한국 개발 환경에 맞게 재구성한 것이다. 원본 크레딧은 저자에게 있다.

728x90
반응형
Share Link
reply
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31