코딩 에이전트 API 키, 어디까지 맡겨도 되나 — 유출 사고(공개 파일 1,122개)로 보는 시크릿 관리 5단계

  • Post last modified:2026년 11월 14일
  • Post category:기술

2026년 11월 7일 보안업체 Apiiro가 공개 GitHub 저장소에서 데이터베이스 비밀번호, API 토큰, 루트 로그인 같은 시크릿이 평문으로 담긴 Claude Code 설정 파일 1,122개를 찾아냈다고 발표했다. 해킹도, 에이전트의 버그도 아니었다. 개발자가 권한 승인 창에서 “Yes, and don’t ask again”(이 명령은 더 묻지 않고 허용)을 눌렀고, 그 승인이 비밀번호를 포함한 명령어 그대로 프로젝트 안의 설정 파일에 저장됐으며, 그 파일이 git에 커밋돼 퍼져나간 것이다.

결론부터 말하면, 코딩 에이전트에서 API 키를 완전히 숨기는 구성은 현실적으로 없다. 에이전트는 내 계정으로 셸을 실행하고 파일을 읽고 환경변수를 보는 전권 작업자다. 그래서 목표를 “숨기기”에서 다음 세 가지로 바꿔야 한다. 첫째, 키가 코드와 설정 파일에 평문으로 남지 않게 한다. 둘째, 승인 없이 키를 외부로 실어 보내는 경로를 차단한다. 셋째, 유출이 확인되면 즉시 무효화·재발급(회전)할 수 있는 상태를 유지한다. 이 글은 사고의 연쇄 구조를 분해하고, Claude Code·Codex CLI·Gemini CLI 세 도구의 공식 문서 기준으로 5단계 구성을 정리한다.

핵심 요약 — 30초 결론

점검 항목왜 위험한가오늘 할 일
“다시 묻지 않기” 승인허용 규칙이 비밀번호 포함 명령 그대로 .claude/settings.local.json에 평문으로 저장된다. Claude Code가 자동으로 추가하는 건 전역 git 제외 목록이라 새 클론·다른 머신에서는 커밋된다저장소 .gitignore에 .claude/settings.local.json 직접 추가, git ls-files로 추적 여부 확인
프로젝트 .env에이전트는 셸을 쓰므로 파일·환경변수 모두 접근한다Read(./.env) deny 규칙 + 샌드박스 읽기 제한, 진짜 자격증명은 작업 디렉터리 밖에 둔다
외부 전송(curl·WebFetch)허용된 셸에서 키를 실어 어디로든 보낼 수 있다curl·wget deny + WebFetch(domain:…) 허용 도메인 지정, Codex는 네트워크 기본 차단 값을 유지
홈 디렉터리 자격증명Claude Code 샌드박스 기본값도 ~/.ssh, ~/.aws/credentials 읽기를 허용한다샌드박스 자격증명 보호 설정, 프로젝트별 최소 권한 키로 분리
키 발급 구조한 키를 여러 곳에 쓰면 유출 시 회전 범위가 전부가 된다프로젝트별 키 분리, 지출 한도 설정, 미사용 키 폐기

무슨 일이 있었나 — 승인 버튼에서 공개 저장소까지

Apiiro 조사와 Ars Technica 보도, 해커뉴스 토론에서 확인되는 사실 관계는 세 단계로 요약된다.

  1. 승인이 파일이 된다. Claude Code는 셸 명령 실행 전 허용을 묻는다. 이때 “Yes, and don’t ask again for: …”을 고르면 그 명령 패턴이 allow 규칙으로 프로젝트 루트의 .claude/settings.local.json에 저장된다. 명령어 안에 -p <비밀번호>나 Authorization: Bearer <토큰> 같은 값이 있으면 그대로 평문으로 남는다.
  2. 보호는 내 컴퓨터에만 적용된다. 공식 문서 기준 Claude Code는 이 파일을 처음 쓸 때 전역 git 제외 목록(core.excludesFile 또는 ~/.config/git/ignore)에 추가한다. 저장소의 .gitignore가 아니다. 그래서 새로 클론한 머신, 동료의 컴퓨터, CI에서는 이 제외 목록이 없어 파일을 일반 파일처럼 커밋한다. 파일을 손으로 만든 경우에는 자동 제외조차 적용되지 않는다.
  3. 공개되면 끝난다. 공개 저장소에 올라온 Claude Code 설정 파일 사본이 10만 건 이상 확인됐고, 이 중 63개 조직의 파일 1,122개에서 실제 시크릿이 발견됐다. Apiiro 연구자가 추적한 1,105개 저장소 가운데 539개에서는 Claude가 참여한 커밋(co-authored-by 표시)이 이 파일을 변경했다.
유출 연쇄 구조와 각 단계별 차단 지점 구성도
Claude Code “다시 묻지 않기” 승인이 공개 저장소 노출로 이어지는 연쇄와, 각 지점의 차단 수단

*출처: Apiiro 조사(2026-11) 및 Claude Code 공식 문서 기반 재구성*

프레임은 정확히 잡아야 한다. 해커뉴스 토론에서도 지적됐듯 “다시 묻지 않기” 기능이 키를 유출한 것이 아니라, 사용자가 비밀을 포함한 명령을 통째로 승인했고 그 기록이 커밋된 것이다. 다만 “승인 이력이 프로젝트 파일로 남는다”는 구조를 모르면 같은 사고가 누구에게나 재현된다. 기능이 아니라 구조의 문제다.

왜 .env 하나로는 안 끝나는가

개발자 커뮤니티에서 반복되는 질문은 이런 형태다. “에이전트가 .env를 읽을 수 있는 건 알겠는데, 이제 어떡하죠?” / “코딩 에이전트를 여러 개 쓰는데 API 키를 어떻게 관리하세요?” / “MCP 서버에 넘기는 키는 어디에 두나요?”

답의 전제는 하나다. 코딩 에이전트는 브라우저 탭이 아니라 내 계정으로 셸을 실행하는 작업자다. 파일 읽기 도구를 막아도 cat .env는 셸 명령이라 별개 경로고, 환경변수로 감춰도 printenv로 읽는다. Claude Code 공식 문서도 이 경계를 명시한다. .claudeignore 파일은 효과가 없으니 Read(./.env) 같은 deny 규칙을 쓰라고 안내하지만, 그 규칙은 파일 도구(Read·Edit)에만 적용된다. 셸 접근까지 좁히려면 샌드박스가 필요하다.

그래서 전략은 “에이전트에게서 키를 숨긴다”가 아니라 다음 3축이 된다.

  • 범위 축소: 키를 프로젝트별로 쪼개고 권한·한도를 최소화한다 (1~2단계)
  • 경로 차단: 외부 전송이 일어나는 창구를 승인 규칙과 샌드박스로 고정한다 (3~4단계)
  • 회전 준비: 유출을 전제로 즉시 무효화할 수 있는 상태를 만든다 (5단계)

5단계 실전 구성 — 공식 문서 기준

1단계: 키를 줄이고 쪼갠다

유출 사고의 피해 규모는 “그 키가 어디까지 쓸 수 있었는가”로 결정된다. 그러니 첫 단계는 도구 설정보다 앞선 키 관리다.

  • 프로젝트별·용도별 별도 키: 사이드 프로젝트마다 키를 분리 발급한다. 에이전트가 작업하는 저장소에는 그 저장소용 키만 둔다.
  • DB 계정 분리: 에이전트 작업용 데이터베이스 계정은 읽기 전용 또는 작업 스키마로 제한한다. 운영 루트 계정을 작업 디렉터리에 두지 않는다.
  • 지출 한도 설정: 각 키에 소비 상한을 건다. 비용 폭주 방지와 회전 비용 축소가 동시에 된다. 구체적 설정법은 코딩 에이전트 요금 폭주, 실행 전에 막는 법에서 다룬다.
  • 미사용 키 폐기: 콘솔에서 마지막 사용이 오래된 키는 지운다. 관리 대상이 적어야 유출 시 판단이 빠르다.

Gemini CLI는 인증 선택지가 세 가지다. 구글 계정 OAuth 로그인, AI Studio API 키(GEMINI_API_KEY 환경변수), Vertex AI(ADC·서비스계정). 개인 프로젝트라면 API 키를 로컬 파일에 두지 않아도 되는 OAuth 로그인이 기본값으로 합리적이다.

2단계: 저장 위치를 고정하고 커밋을 끊는다

이번 사고의 직접 원인은 “파일이 어디에 저장되는지 모른 채 승인”이었다. 저장 위치를 명시적으로 통제한다.

  • .env는 저장소 .gitignore 최상단에: 커밋용 템플릿은 이름만 남긴 .env.example로 분리한다. Gemini CLI는 프로젝트의 .gemini/.env 또는 ~/.gemini/.env를 자동으로 읽으니 이 경로도 제외 목록에 포함한다.
  • .claude/settings.local.json을 저장소 .gitignore에 직접 추가: 공식 문서도 “파일을 손으로 만들었다면 직접 .gitignore에 추가하라”고 명시한다. 자동 추가되는 전역 제외 목록은 내 머신의 커밋만 막는다.
  • 이미 추적 중인지 확인:
git ls-files | grep -E 'settings\.local|\.env$|\.claude/'

출력이 있으면 이미 git이 추적 중이니 git rm --cached <파일>로 추적을 끊는다.

  • 커밋 직전 자동 스캔: gitleaks를 프리커밋 훅이나 CI에 붙이고, GitHub 저장소는 secret scanning 알림을 켠다. 사람의 습관보다 자동화가 먼저다.

3단계: 승인 규칙으로 전송 경로를 고정한다

키가 새어 나가는 순간은 대부분 “외부로 보내는 명령”이다. 이 창구를 규칙으로 고정한다. Claude Code 공식 문서가 권장하는 패턴은 셸 네트워크 도구 차단 + WebFetch 도메인 허용의 조합이다.

{
  "permissions": {
    "deny": [
      "Bash(curl *)",
      "Bash(wget *)",
      "Read(./.env)",
      "Read(./secrets/**)"
    ],
    "allow": [
      "WebFetch(domain:api.example.com)"
    ]
  }
}
  • deny와 allow는 세트로: WebFetch만 허용해도 Bash가 열려 있으면 curl로 우회할 수 있다. 공식 문서도 “WebFetch만 쓰라고 해도 Bash가 허용되면 curl·wget으로 어느 URL이든 접근 가능하다”고 명시한다.
  • 중첩 명령도 막힌다: deny·ask 규칙은 서브셸, 명령 치환, for 문 안에 포함된 하위 명령에도 적용된다. cd /tmp && git clean -f 같은 우회도 승인 대상이 된다.
  • 더 정교한 차단은 PreToolUse 훅: 도구 실행 전에 명령어를 검사해 permissionDecision: "deny"를 반환하는 훅을 만들 수 있다. 공식 문서의 예시 패턴 그대로다.
  • Codex CLI: 승인 정책 on-request를 유지하고 --ask-for-approval never는 쓰지 않는다. 대화형 읽기 전용 작업은 codex --sandbox read-only --ask-for-approval on-request로 명시한다.
  • Gemini CLI: 승인 모드 --approval-mode의 선택지는 default, auto_edit, yolo, plan이다. yolo(전체 자동 승인)는 격리 환경에서만 쓴다.

4단계: 샌드박스로 셸 접근 범위를 줄인다

승인 규칙은 “묻는 창구”를 통제할 뿐, 한 번 허용된 셸이 닿는 범위 자체는 줄여주지 않는다. 범위는 운영체제 수준 샌드박스가 담당한다.

  • Claude Code: Bash 샌드박스가 있지만 기본값은 꺼져 있다. 세션에서 /sandbox를 실행하거나 설정 파일에 sandbox.enabled: true를 넣어 켠다. 켜면 셸 명령의 쓰기는 작업 디렉터리와 임시 디렉터리로 제한되고, 네트워크는 허용 도메인 화이트리스트(초기값 비어 있음)를 통해서만 나간다. 주의할 기본값 하나: 샌드박스를 켜도 파일 읽기는 머신 대부분이 허용되며 ~/.ssh와 ~/.aws/credentials도 포함이다. 자격증명 보호 설정이나 denyRead로 명시적으로 막는다.
  • Codex CLI: 반대로 샌드박스가 기본값이다. 쓰기는 워크스페이스로 제한되고 네트워크는 꺼져 있다. 외부 접근이 필요하면 설정에 [sandbox_workspace_write] network_access = true를 명시적으로 넣어야 한다. “기본이 닫힌 구성”이라 이번 사고 유형에 구조적으로 강하다.
  • Gemini CLI: Docker·Podman 컨테이너 샌드박스를 지원한다(GEMINI_SANDBOX=docker). macOS는 Seatbelt 프로파일이 기본 적용된다. 컨테이너 이미지 단위로 격리하면 자격증명 파일 접근 원천 차단과 가장 가깝다.
  • 전면 허용 모드는 격리 환경 전용: Claude Code의 bypassPermissions, Codex의 --ask-for-approval never, Gemini CLI의 yolo는 공식 문서가 모두 컨테이너·VM 같은 격리 환경 전용으로 안내하는 값이다. 일반 머신에서 쓰면 이 글의 모든 안전장치가 무의미해진다.

근본 대책은 설정보다 구조다. ~/.aws, ~/.ssh, ~/.kube 같은 자격증명이 있는 홈 디렉터리에서 에이전트를 돌리기보다, 작업 전용 디렉터리와 전용 계정으로 경계를 만드는 것이 샌드박스 설정 이전의 원칙이다.

5단계: 유출을 전제로 회전 준비를 한다

마지막 단계는 “막았다고 믿는” 단계가 아니라 “이미 샀을 수 있다”를 전제로 한 대응 준비다.

  • 이미 공개됐는지 점검: git ls-files | grep claude로 설정 파일 추적 여부를 보고, GitHub 저장소에서 .claude/settings.local.json을 검색해본다. 조직 저장소라면 secret scanning 알림을 확인한다.
  • 유출 확인 시 순서: 키 즉시 무효화(revoke)가 먼저, 커밋 히스토리 정리는 그다음이다. 이미 복사됐을 수 있는 키는 히스토리에서 지워도 소용이 없다. 무효화 → 재발급 → 필요 시 git filter-repo 등으로 히스토리 정리 순서로 진행한다.
  • 키 3개 이상·팀 단위라면 시크릿 매니저 검토: 1Password·Infisical·클라우드 시크릿 매니저 류를 쓰면 키를 파일 대신 실행 시점에 주입할 수 있다. 개인 소규모 프로젝트라면 1~4단계로 충분하다.
  • 주기적 노출 판정: “공개해도 되는가”를 한 번만 판단하는 게 아니라 주기적으로 다시 보는 절차가 필요하다는 점은 셀프호스팅 서비스 노출 판정 5단계와 같은 구조다.

에이전트가 쓴 코드를 사람이 검수하는 게이트와도 연결된다. 코드 품질 게이트는 에이전트가 쓴 코드, 누가 검수하나에서, 이 글은 자격증명 게이트를 다룬다.

도구별 기본값 한눈에 보기

구분Claude CodeCodex CLIGemini CLI
승인 기록이 남는 파일.claude/settings.local.json(프로젝트 루트)~/.codex/config.toml(sandbox·approval 설정).gemini/.env, ~/.gemini/.env(키), OAuth 캐시
네트워크 기본값승인제. 샌드박스 켜면 도메인 화이트리스트기본 차단. network_access = true 필요승인제. 샌드박스 이미지·프로파일로 통제
셸 파일 읽기 제어Read deny 규칙(파일 도구) + 샌드박스 denyRead샌드박스 read-only 모드컨테이너 격리(GEMINI_SANDBOX=docker)
샌드박스 기본 상태꺼짐(/sandbox로 켬, bubblewrap 기반)켜짐(OS 수준, 쓰기 워크스페이스 한정)macOS Seatbelt 기본, Docker 옵션
전면 허용 위험 값bypassPermissions--ask-for-approval never--approval-mode yolo

같은 “자동 승인”이라도 세 도구의 기본 구조가 다르다. Codex는 닫힌 기본값에서 열고, Claude Code는 열린 기본값에서 닫으며, Gemini CLI는 격리 수단을 별도로 붙인다. 이 차이를 알면 도구를 바꿀 때 다시 점검할 지점이 보인다.

10분 점검 체크리스트

  1. 저장소 .gitignore에 .claude/settings.local.json과 .env가 직접 들어 있는가
  2. git ls-files | grep -E 'settings\.local|\.env$' 출력이 비어 있는가
  3. 에이전트가 쓰는 키가 프로젝트별로 분리되어 있고 지출 한도가 걸려 있는가
  4. curl·wget deny 규칙과 WebFetch(domain:…) 허용 도메인이 세트로 되어 있는가
  5. Claude Code 샌드박스가 켜져 있고 ~/.ssh·~/.aws/credentials 읽기가 막혀 있는가
  6. Codex를 쓴다면 network_access를 켠 저장소가 어디인지 알고 있는가
  7. gitleaks 프리커밋 훅 또는 CI 시크릿 스캔이 동작하는가
  8. 유출 시 각 키를 어느 콘솔에서 즉시 무효화하는지 적어두었는가

자주 묻는 질문

“다시 묻지 않기”를 아예 쓰지 말아야 하나요?

아니다. 반복 작업의 승인 피로를 줄이는 기능 자체는 필요하다. 문제는 승인 내용이 프로젝트 파일에 남는다는 구조를 모르는 것이다. “다시 묻지 않기”를 누르기 전에 명령어에 비밀이 포함됐는지 보고, 승인이 저장되는 파일을 .gitignore로 통제하면 된다. 비밀 포함 명령은 아예 승인하지 않고, 키를 인자로 쓰지 않는 방식으로 작업을 바꾸는 게 근본 대책이다.

.env를 에이전트가 못 읽게 완전히 막을 수 있나요?

에이전트가 셸을 쓰는 한 100% 차단은 없다. Read(./.env) deny 규칙은 파일 도구만 막고, cat .env는 셸 경로라 별개다. 셸 경로까지 좁히려면 Claude Code 샌드박스 denyRead, Codex read-only 모드, Gemini CLI 컨테이너 격리가 필요하고, 그래도 결정적 차단은 컨테이너·VM 격리다. 현실적인 목표는 “못 읽게”가 아니라 “읽어도 그 키의 권한과 한도가 작게”다.

시크릿 매니저를 꼭 도입해야 하나요?

키가 세 개 이상이거나 팀이 함께 쓴다면 도입 가치가 있다. 실행 시점 주입으로 파일 평문을 없애고 회전도 중앙에서 할 수 있어서다. 개인 사이드 프로젝트 하나에 키 한두 개라면 이 글의 1~4단계(분리·차단·샌드박스)가 우선이고, 매니저는 그다음이다.

참고자료

기준일: 2026-11-14. 도구 설정값은 각 공식 문서의 당일 기준이며 이후 변경될 수 있다.

답글 남기기