API 키를 깃허브에 올렸을 때, 순서는 삭제가 아니라 무효화입니다

API 키를 깃허브에

커밋 메시지를 쓰고 푸시 버튼을 누른 다음에야, 방금 올린 코드 안에 .env 파일이나 설정 파일 안에 적어둔 값이 API 키였다는 걸 알아차리신 적 있으신가요. API 키를 깃허브에 올렸을 때 가장 먼저 떠오르는 대응은 보통 “커밋을 지우고 히스토리를 깨끗하게 되돌리자”이지만, 그 순서부터 잘못됐습니다. 키가 한 번이라도 올라갔다면 히스토리를 아무리 정리해도 이미 노출된 값 자체는 사라지지 않기 때문에, 삭제 작업보다 키 무효화와 재발급을 먼저 끝내야 합니다.

API 키를 깃허브에 올렸을 때 순서는 삭제가 아니라 무효화입니다

공개 저장소에 키가 올라가는 순간, 그 값은 이미 저장소 바깥으로 나간 것으로 봐야 합니다. 깃허브는 퍼블릭 저장소에 올라온 코드를 상시 훑어서 알려진 서비스의 키 패턴을 찾아내는 시크릿 스캐닝 기능을 운영하고 있는데, 이건 깃허브만 보는 게 아닙니다. 외부 크롤러나 자동화 스크립트도 공개 커밋을 상시 수집하고 있어서, 사용자가 문제를 눈치채고 히스토리 정리에 들어가기 전에 이미 값이 복제됐을 가능성을 염두에 둬야 합니다.

그래서 처리 순서를 “삭제 → 안심”이 아니라 “무효화 → 재발급 → 그다음에 정리”로 바꿔야 합니다. 저장소에서 파일을 지우거나 커밋을 되돌리는 작업은 겉으로 보이는 코드를 정리하는 일이고, 키 자체의 권한을 끊는 일은 아닙니다. 발급한 서비스 쪽 콘솔에 들어가서 그 키를 비활성화하기 전까지는, 깃허브에서 흔적을 지웠다고 해도 그 키는 여전히 살아서 작동합니다.

히스토리부터 지우면 정말 안전해질까요?

git filter-repo나 BFG Repo-Cleaner로 커밋 히스토리를 재작성하면 본인 로컬과 원격 저장소에서는 그 파일이 들어있던 커밋이 사라진 것처럼 보입니다. 하지만 이 작업은 커밋 해시를 바꾸는 작업일 뿐, 이미 그 커밋을 내려받았던 다른 사람의 로컬 클론, 포크해 간 저장소, CI 빌드 로그, 검색엔진 캐시에는 손이 닿지 않습니다. 깃허브 공식 문서도 이런 민감한 데이터를 지우는 절차를 안내하면서, 히스토리 재작성에 앞서 노출된 값 자체를 교체하라고 적고 있습니다.

노트북 화면의 보안 경고를 발견하고 당황한 개발자

즉 히스토리 재작성은 “앞으로 이 저장소를 새로 클론하는 사람에게는 안 보이게 만드는” 정리 작업에 가깝고, “이미 유출된 값을 무력화하는” 작업은 아닙니다. 두 작업의 목적이 다르다는 걸 구분하지 못하면, 히스토리를 깨끗하게 만들어 놓고도 여전히 뚫려 있는 키를 그대로 쓰는 상황이 생깁니다. 특히 팀 저장소에서 강제 푸시로 히스토리를 바꾸면 다른 팀원의 로컬 브랜치가 꼬이는 부작용도 따라오기 때문에, 급하지 않다면 이 작업은 키 무효화 뒤로 미뤄도 됩니다.

키 무효화 후 새 키를 발급받는 절차

실제로 처리할 때는 다음 순서를 그대로 따라가시면 됩니다.

  1. 어떤 서비스의 키인지 먼저 확인합니다. AWS 액세스 키, OpenAI API 키, Google Cloud 키마다 무효화하는 콘솔 위치가 다르기 때문에, 서비스명을 모른 채로 움직이면 시간을 허비합니다.
  2. 해당 서비스 콘솔에서 그 키를 즉시 비활성화하거나 삭제합니다. 이 단계가 끝나야 유출된 값이 실제로 쓸모없어집니다.
  3. 새 키를 발급받고, 코드에는 다시 적지 않습니다. 환경 변수나 비밀값 관리 체계에 저장해야 같은 일이 반복되지 않는데, 이런 보관 방식은 OWASP도 비밀값 관리 가이드로 정리해 두고 있습니다.
  4. 저장소에서 어떤 커밋에 키가 들어있었는지 확인하고, 추적 중이던 파일이라면 git rm --cached로 깃의 추적 대상에서 뺍니다.
# 어떤 커밋에서 키가 들어갔는지 확인
git log --all --full-history -- .env

# 이미 추적되고 있는 파일이면 추적만 해제 (파일은 로컬에 남음)
git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: .env 추적 해제"

이 단계까지 끝낸 다음에야 히스토리 재작성을 고민해도 늦지 않습니다. 재발급된 새 키는 무효화한 옛 키와 별개의 값이기 때문에, 과거 커밋에 흔적이 남아 있어도 더 이상 작동하지 않는 값이라 실질적인 위험은 크게 줄어듭니다.

git filter-repo, BFG, 깃허브 알림 처리 — 상황별로 골라보세요

코드 에디터에서 환경 변수와 API 키를 안전하게 관리하는 화면

키 무효화를 끝낸 뒤 히스토리까지 정리하고 싶다면, 상황에 맞는 도구를 고르시면 됩니다.

방법 처리 범위 설치/준비 주의할 점
git filter-repo 지정한 파일·패턴을 전체 히스토리에서 제거 Python 환경에 pip install git-filter-repo 필요 작업 후 원격에 강제 푸시 필요, 협업 중이면 팀원에게 재클론 안내
BFG Repo-Cleaner 큰 파일이나 특정 문자열을 빠르게 제거 Java 실행 환경 필요, jar 파일 직접 실행 세밀한 경로 지정보다 패턴 삭제에 특화돼 있어 복잡한 조건엔 약함
깃허브 알림(시크릿 스캐닝) 처리 알려진 키 패턴을 감지해 알림만 제공 별도 설치 없이 퍼블릭 저장소에 기본 적용 히스토리 자체는 그대로 남고, 비공개 저장소는 Advanced Security 같은 별도 기능 없이는 기본 지원되지 않음

표에서 보듯 셋 중 어느 것도 “키 자체를 무력화”하지는 않습니다. 모두 코드 저장소 쪽을 정리하거나 알려주는 도구이고, 실제 무력화는 앞서 다룬 서비스 콘솔에서의 비활성화로만 끝납니다. 팀 저장소를 쓰고 있다면 강제 푸시 전에 다른 작업자들의 브랜치 상태를 먼저 확인하는 쪽이 안전합니다.

포크되거나 미러링된 저장소까지 안심할 수 있나요?

여기서부터는 히스토리 재작성으로 해결되지 않는 영역입니다. 누군가 유출 사실을 알기 전에 그 저장소를 포크했거나, 외부 미러링 서비스가 코드를 복제해 갔다면, 원본 저장소의 히스토리를 아무리 깨끗하게 바꿔도 그 복제본에는 옛 커밋이 그대로 남아 있습니다. 포크된 저장소의 히스토리까지 소유자가 강제로 바꿀 권한은 없기 때문에, 이 경우 유일하게 확실한 대응은 역시 키 무효화뿐입니다.

비공개 저장소라고 완전히 안심할 수도 없습니다. 비공개 저장소의 시크릿 스캐닝은 공개 저장소처럼 기본으로 켜져 있지 않고, 별도의 보안 기능을 추가로 적용해야 작동하는 경우가 많습니다. 즉 “비공개니까 괜찮겠지”라는 가정으로 발견이 늦어질 수 있다는 뜻이라, 비공개 저장소를 쓰더라도 키가 커밋에 들어가는 상황 자체를 사전에 막는 쪽이 훨씬 안전합니다.

지금 당장 할 일을 정리하면, 유출된 서비스 콘솔에 들어가 키를 비활성화하고 새 키를 발급받아 .gitignore로 관리하는 작업이 먼저입니다. 히스토리 재작성은 팀 상황을 봐 가며 그다음에 처리해도 늦지 않고, 커밋 전에 git diff --cached로 올라갈 내용을 한 번 더 확인하는 습관을 들이면 같은 상황이 반복되는 걸 줄일 수 있습니다.

LLM API 키가 유출됐을 때, 로테이션과 사용량 이상 탐지부터 챙기세요

참고: API 키를 깃허브에 — 위키백과

Leave a Comment