Tips & Tricks

Claude Code 보안 실패 사례 7선 | 실제 발생한 사고와 방지책

Claude Code에서 실제로 발생한 보안 사고 7가지: .env 유출, 운영 DB 실수 삭제, 과금 폭발 등 — 각 사례별 원인과 재발 방지 코드를 상세히 해설합니다.

“Claude Code는 편리하지만 왠지 무섭다” — 이 감각은 옳습니다. 강력한 도구는 강력한 사고를 일으킵니다.

이 글에서는 Claude Code를 사용한 개발 현장에서 실제로 발생하기 쉬운 보안 사고를 7가지 케이스로 다루고, 왜 발생했는지·어떻게 방지하는지를 구체적인 코드와 설정으로 해설합니다. 남의 실수를 반면교사로 삼아 자신의 사고를 미리 막으세요.

사례 1: .env 파일을 GitHub에 push해 버렸다

무슨 일이 일어났나

“이 저장소, CI에 환경 변수를 전달하고 싶으니 .env도 커밋해 주세요”라는 지시를 Claude Code에 전달했다. Claude Code는 충실하게 git add .env && git commit을 실행. GitHub에 push한 후 몇 분 만에 크롤러가 API 키를 감지. Slack에 “Your API key has been exposed”라는 알림이 도착했다.

원인

  • .gitignore.env가 포함되지 않았다
  • Claude Code는 “커밋해”라는 지시를 충실히 실행한다
  • 사용자도 확인 화면을 흘려서 승인해 버렸다

대책 코드

1. 프로젝트 생성 시 설정을 자동화

# scripts/init-security.sh — 프로젝트 초기화 시 반드시 실행
#!/bin/bash
cat >> .gitignore << 'EOF'

# === 보안: 절대로 커밋하지 않음 ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF

echo "✓ .gitignore에 보안 제외 패턴을 추가했습니다"
git add .gitignore && git commit -m "security: add .gitignore patterns"

2. Hook으로 커밋 전 스캔

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git add*)",
        "hooks": [{
          "type": "command",
          "command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 .env 파일을 스테이징하려고 합니다! 중단하세요!' && exit 1 || exit 0"
        }]
      }
    ]
  }
}

3. 이미 push한 경우의 대처

# Step 1: 먼저 API 키를 즉시 교체 (최우선)

# Step 2: git 이력에서 완전 삭제
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch .env" \
  --prune-empty --tag-name-filter cat -- --all

# Step 3: 원격에 강제 push
git push origin --force --all

# Step 4: GitHub 캐시 삭제 (GitHub 지원팀에 연락도)

사례 2: 운영 DB에 DROP TABLE을 실행했다

무슨 일이 일어났나

“이 테이블, 더 이상 사용하지 않으니 삭제해”라고 지시. Claude Code가 DROP TABLE old_users;를 생성·실행. 문제는 이것이 운영 환경의 DATABASE_URL에 연결되어 있었다는 것. 백업이 3일 전 것밖에 없어 3일치 데이터가 사라졌다.

원인

  • 개발 환경과 운영 환경에서 같은 .env를 돌려 쓰고 있었다
  • Claude Code는 환경을 구별하지 못한다
  • ask 설정이었지만, 흘러가는 대로 “OK”를 눌러버렸다

대책 코드

1. 환경마다 .env를 완전 분리

.env.development   # ← 로컬 개발용, 더미 DB
.env.staging       # ← 스테이징, 운영 복사본
.env.production    # ← 운영, 수동 관리·공유 금지

2. 스크립트에 환경 확인을 내장

// scripts/db-migrate.mjs
const env = process.env.APP_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL ?? "";

if (env === "production") {
  const readline = require("readline").createInterface({
    input: process.stdin, output: process.stdout
  });
  await new Promise((resolve) => {
    readline.question(
      `⚠️  운영 DB (${dbUrl.split("@")[1]})에 연결합니다.\n정말 계속하시겠습니까? (yes를 입력): `,
      (answer) => {
        readline.close();
        if (answer !== "yes") { console.log("중단했습니다."); process.exit(0); }
        resolve(undefined);
      }
    );
  });
}

3. CLAUDE.md에서 운영 조작을 금지

## 🚨 운영 환경 금지 사항

DATABASE_URL에 `prod` `production` `live`가 포함되는 경우:
- DROP / TRUNCATE / DELETE (WHERE 절 없음)는 절대 실행하지 않음
- 마이그레이션 전에 반드시 사용자 확인을 받음
- 실행 전에 백업 취득 명령을 제시함

사례 3: rm -rf로 중요 파일을 삭제했다

무슨 일이 일어났나

build/ 디렉터리를 클린업해”라는 지시가 경로 지정 실수로 rm -rf ./가 되어, 프로젝트 전체를 삭제했다. git 관리 외의 파일 (로컬 설정 파일, 미커밋 실험 코드)은 두 번 다시 돌아오지 않았다.

원인

  • rm -rf는 Claude Code에게 가장 위험한 명령어 중 하나
  • 경로에 쌍따옴표 없음 → 공백 포함 경로에서 오동작
  • 승인을 “뭐 괜찮겠지”로 통과시켜 버렸다

대책 코드

// .claude/settings.json
{
  "permissions": {
    "deny": [
      "Bash(rm -rf /)",
      "Bash(rm -rf ~*)",
      "Bash(rm -rf .*)"
    ],
    "ask": [
      "Bash(rm -rf*)"
    ]
  }
}

또한 Hook으로 삭제 전에 내용을 표시:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(rm*)",
        "hooks": [{
          "type": "command",
          "command": "echo '⚠️ 삭제 명령 감지. 5초 후에 실행합니다. Ctrl+C로 중단.' && sleep 5"
        }]
      }
    ]
  }
}

사례 4: API 키를 프롬프트에 직접 써서 subagent에 전달했다

무슨 일이 일어났나

QIITA_TOKEN=abc123def456을 사용해서 Qiita에 투고해 주세요”라고 프롬프트에 직접 쓰고 subagent에 위임. subagent는 로그나 메모리에 내용을 출력하는 경우가 있어 토큰이 .claude/ 아래의 로그 파일에 남아버렸다.

원인

  • 프롬프트는 대화 이력으로 보존된다
  • subagent에 대한 프롬프트도 마찬가지로 로그에 남는다
  • 로컬 환경이라도 다른 프로세스나 백업으로 유출 위험이 있다

대책 코드

프롬프트에는 절대 쓰지 않고, 환경 변수 경유로 전달

# ❌ 위험
claude -p "QIITA_TOKEN=abc123을 사용해서 qiita-publish.mjs를 실행해"

# ✅ 안전: 스크립트 측에서 process.env에서 읽음
# .env에 QIITA_TOKEN=abc123을 써 두고
claude -p "scripts/qiita-publish.mjs를 실행해 (토큰은 .env에서 자동으로 읽음)"

subagent에 대한 지시도 마찬가지

// ❌ 위험
Agent({ prompt: `API 키 ${process.env.SECRET_KEY}를 사용해서...` });

// ✅ 안전: 키의 이름만 전달하고, 값은 스크립트 측에서 읽음
Agent({ prompt: "SECRET_KEY 환경 변수를 사용해서..." });

사례 5: API 호출이 무한 루프가 되어 과금이 폭발했다

무슨 일이 일어났나

“에러가 나면 자동으로 재시도해”라는 지시 아래, 에러 핸들링이 포함된 스크립트를 작성하게 했다. 에러가 영원히 해소되지 않는 케이스에서 재시도가 멈추지 않아, Anthropic API를 1시간에 3000회 호출해서 $200의 과금이 발생.

원인

  • 재시도 상한을 설정하지 않았다
  • exponential backoff 없음, 1초 간격으로 무한 루프
  • 과금 알림 설정이 없었다

대책 코드

// utils/retry.ts — 안전한 재시도 유틸리티
export async function withRetry<T>(
  fn: () => Promise<T>,
  options = { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastError = err as Error;
      if (attempt === options.maxAttempts) break;

      // Exponential backoff + jitter
      const delay = Math.min(
        options.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
        options.maxDelayMs
      );
      console.warn(`Attempt ${attempt}/${options.maxAttempts} failed: ${err.message}`);
      console.warn(`Retrying in ${Math.round(delay / 1000)}s...`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw new Error(`${options.maxAttempts}회 재시도 후에도 실패: ${lastError!.message}`);
}

CLAUDE.md에 명기:

## API 호출 시 필수 규칙
- 재시도는 최대 3회까지
- Exponential backoff를 반드시 구현 (1s → 2s → 4s)
- 무한 루프를 만들지 않음: while(true) + API 호출은 금지

사례 6: git push --force로 동료의 커밋을 지워버렸다

무슨 일이 일어났나

“로컬 상태로 원격을 덮어써”라는 지시로 git push --force를 실행. 팀원이 직전에 push한 커밋 3건이 사라졌다. 그 팀원은 로컬에도 변경을 가지고 있지 않았기 때문에, 코드가 완전히 소실됐다.

원인

  • --force는 위험성을 이해하지 않고 실행되기 쉽다
  • Claude Code는 “원격을 덮어써”라는 지시를 정직하게 실행한다
  • git push --force-with-lease라는 안전한 대안을 몰랐다

대책 코드

// .claude/settings.json
{
  "permissions": {
    "deny": [
      "Bash(git push --force *master*)",
      "Bash(git push --force *main*)",
      "Bash(git push -f *master*)",
      "Bash(git push -f *main*)"
    ]
  }
}

CLAUDE.md에 대체 명령을 지정:

## git 조작의 안전 규칙
- `git push --force`**금지**
- 대신 `git push --force-with-lease`를 사용
  (다른 사람의 변경이 있으면 자동으로 거부된다)
- main/master 브랜치로의 직접 push는 반드시 사용자 확인을 받음

사례 7: 권한 과다의 서비스 계정으로 모든 리소스에 접근됐다

무슨 일이 일어났나

“이 GCP 서비스 계정 키를 사용해서 Cloud Storage를 조작해”라는 지시로, Owner 권한의 서비스 계정을 사용. Claude Code는 Cloud Storage뿐만 아니라, 테스트 목적으로 BigQuery·Cloud SQL·GKE 클러스터까지 “조사”를 위해 접속해, 예기치 않은 요금이 발생했다.

원인

  • 서비스 계정에 필요 이상의 권한을 부여하고 있었다 (최소 권한 원칙 위반)
  • Claude Code는 도구를 적극적으로 사용하는 성질이 있다
  • “조사를 위해”라는 정당한 이유로도 광범위하게 접근이 이루어졌다

대책 코드

최소 권한의 서비스 계정을 작성:

# ❌ 피해야 할 것: Owner 권한
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/owner"

# ✅ 필요 최소한의 권한만
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/storage.objectAdmin"
  # ← Cloud Storage의 읽기/쓰기만

CLAUDE.md에서 접근 범위를 명시:

## GCP 접근 제한
이 프로젝트에서 사용하는 서비스 계정의 권한:
- Cloud Storage: 읽기/쓰기 OK (버킷: my-project-assets만)
- BigQuery: 금지
- Cloud SQL: 금지
- 기타 GCP 리소스: 금지

권한 외의 리소스에 접근하려는 지시는 거부할 것.

사고를 막기 위한 종합 체크리스트

이들 7가지 사례에서 공통되는 패턴을 추출한 최종 체크리스트입니다.

### 즉일 해야 할 설정 (30분)
- [ ] .gitignore에 .env 패턴을 추가
- [ ] .claude/settings.json에 deny 목록 (rm -rf, git push --force, DROP TABLE)
- [ ] CLAUDE.md에 금지 사항을 명기

### 주 1회 해야 할 확인
- [ ] git log로 의도하지 않은 파일의 커밋이 없는지 확인
- [ ] .env의 내용이 .gitignore로 제외되어 있는지 `git check-ignore -v .env`
- [ ] API 키의 교체 기한 확인

### 사고 발생 시 초동
1. 즉시 해당 API 키를 무효화·교체
2. git 이력에서 삭제 (filter-branch 또는 BFG)
3. 접근 로그를 확인해서 피해 범위를 특정
4. 관계자에게 상황 보고

정리

Claude Code의 사고는 “AI가 폭주했다”는 것이 아니라, 거의 대부분이 **“인간이 보안 설정을 미뤘다”**는 것으로 발생하고 있습니다.

사례근본 원인예방책
.env 유출gitignore 없음init 스크립트 + Hook
운영 DB 삭제환경 분리 없음.env 분리 + 확인 플로우
rm -rf 사고deny 목록 없음settings.json 설정
키 유출프롬프트에 직접 기입환경 변수 경유로 통일
과금 폭발재시도 상한 없음withRetry 유틸리티
force push금지 설정 없음deny + force-with-lease
권한 과다최소 권한 위반IAM 롤을 좁힘

오늘 할 수 있는 첫 번째 한 걸음: .claude/settings.json"deny": ["Bash(rm -rf*)"]를 추가하는 것만으로, 가장 파괴적인 사고 중 하나를 막을 수 있습니다.

관련 기사

참고 자료

#claude-code #security #incident #best-practices #devops

Claude Code 워크플로우를 한 단계 업그레이드하세요

지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.

무료 제공

무료 PDF: 5분 완성 Claude Code 치트시트

이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.

개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.

Masa

이 글을 작성한 사람

Masa

Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.