
커밋 메시지를 쓰고 푸시 버튼을 누른 다음에야, 방금 올린 코드 안에 .env 파일이나 설정 파일 안에 적어둔 값이 API 키였다는 걸 알아차리신 적 있으신가요. API 키를 깃허브에 올렸을 때 가장 먼저 떠오르는 대응은 보통 “커밋을 지우고 히스토리를 깨끗하게 되돌리자”이지만, 그 순서부터 잘못됐습니다. 키가 한 번이라도 올라갔다면 히스토리를 아무리 정리해도 이미 노출된 값 자체는 사라지지 않기 때문에, 삭제 작업보다 키 무효화와 재발급을 먼저 끝내야 합니다.
API 키를 깃허브에 올렸을 때 순서는 삭제가 아니라 무효화입니다
공개 저장소에 키가 올라가는 순간, 그 값은 이미 저장소 바깥으로 나간 것으로 봐야 합니다. 깃허브는 퍼블릭 저장소에 올라온 코드를 상시 훑어서 알려진 서비스의 키 패턴을 찾아내는 시크릿 스캐닝 기능을 운영하고 있는데, 이건 깃허브만 보는 게 아닙니다. 외부 크롤러나 자동화 스크립트도 공개 커밋을 상시 수집하고 있어서, 사용자가 문제를 눈치채고 히스토리 정리에 들어가기 전에 이미 값이 복제됐을 가능성을 염두에 둬야 합니다.
그래서 처리 순서를 “삭제 → 안심”이 아니라 “무효화 → 재발급 → 그다음에 정리”로 바꿔야 합니다. 저장소에서 파일을 지우거나 커밋을 되돌리는 작업은 겉으로 보이는 코드를 정리하는 일이고, 키 자체의 권한을 끊는 일은 아닙니다. 발급한 서비스 쪽 콘솔에 들어가서 그 키를 비활성화하기 전까지는, 깃허브에서 흔적을 지웠다고 해도 그 키는 여전히 살아서 작동합니다.
히스토리부터 지우면 정말 안전해질까요?
git filter-repo나 BFG Repo-Cleaner로 커밋 히스토리를 재작성하면 본인 로컬과 원격 저장소에서는 그 파일이 들어있던 커밋이 사라진 것처럼 보입니다. 하지만 이 작업은 커밋 해시를 바꾸는 작업일 뿐, 이미 그 커밋을 내려받았던 다른 사람의 로컬 클론, 포크해 간 저장소, CI 빌드 로그, 검색엔진 캐시에는 손이 닿지 않습니다. 깃허브 공식 문서도 이런 민감한 데이터를 지우는 절차를 안내하면서, 히스토리 재작성에 앞서 노출된 값 자체를 교체하라고 적고 있습니다.

즉 히스토리 재작성은 “앞으로 이 저장소를 새로 클론하는 사람에게는 안 보이게 만드는” 정리 작업에 가깝고, “이미 유출된 값을 무력화하는” 작업은 아닙니다. 두 작업의 목적이 다르다는 걸 구분하지 못하면, 히스토리를 깨끗하게 만들어 놓고도 여전히 뚫려 있는 키를 그대로 쓰는 상황이 생깁니다. 특히 팀 저장소에서 강제 푸시로 히스토리를 바꾸면 다른 팀원의 로컬 브랜치가 꼬이는 부작용도 따라오기 때문에, 급하지 않다면 이 작업은 키 무효화 뒤로 미뤄도 됩니다.
키 무효화 후 새 키를 발급받는 절차
실제로 처리할 때는 다음 순서를 그대로 따라가시면 됩니다.
- 어떤 서비스의 키인지 먼저 확인합니다. AWS 액세스 키, OpenAI API 키, Google Cloud 키마다 무효화하는 콘솔 위치가 다르기 때문에, 서비스명을 모른 채로 움직이면 시간을 허비합니다.
- 해당 서비스 콘솔에서 그 키를 즉시 비활성화하거나 삭제합니다. 이 단계가 끝나야 유출된 값이 실제로 쓸모없어집니다.
- 새 키를 발급받고, 코드에는 다시 적지 않습니다. 환경 변수나 비밀값 관리 체계에 저장해야 같은 일이 반복되지 않는데, 이런 보관 방식은 OWASP도 비밀값 관리 가이드로 정리해 두고 있습니다.
- 저장소에서 어떤 커밋에 키가 들어있었는지 확인하고, 추적 중이던 파일이라면
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, 깃허브 알림 처리 — 상황별로 골라보세요

키 무효화를 끝낸 뒤 히스토리까지 정리하고 싶다면, 상황에 맞는 도구를 고르시면 됩니다.
| 방법 | 처리 범위 | 설치/준비 | 주의할 점 |
|---|---|---|---|
| git filter-repo | 지정한 파일·패턴을 전체 히스토리에서 제거 | Python 환경에 pip install git-filter-repo 필요 |
작업 후 원격에 강제 푸시 필요, 협업 중이면 팀원에게 재클론 안내 |
| BFG Repo-Cleaner | 큰 파일이나 특정 문자열을 빠르게 제거 | Java 실행 환경 필요, jar 파일 직접 실행 | 세밀한 경로 지정보다 패턴 삭제에 특화돼 있어 복잡한 조건엔 약함 |
| 깃허브 알림(시크릿 스캐닝) 처리 | 알려진 키 패턴을 감지해 알림만 제공 | 별도 설치 없이 퍼블릭 저장소에 기본 적용 | 히스토리 자체는 그대로 남고, 비공개 저장소는 Advanced Security 같은 별도 기능 없이는 기본 지원되지 않음 |
표에서 보듯 셋 중 어느 것도 “키 자체를 무력화”하지는 않습니다. 모두 코드 저장소 쪽을 정리하거나 알려주는 도구이고, 실제 무력화는 앞서 다룬 서비스 콘솔에서의 비활성화로만 끝납니다. 팀 저장소를 쓰고 있다면 강제 푸시 전에 다른 작업자들의 브랜치 상태를 먼저 확인하는 쪽이 안전합니다.
포크되거나 미러링된 저장소까지 안심할 수 있나요?
여기서부터는 히스토리 재작성으로 해결되지 않는 영역입니다. 누군가 유출 사실을 알기 전에 그 저장소를 포크했거나, 외부 미러링 서비스가 코드를 복제해 갔다면, 원본 저장소의 히스토리를 아무리 깨끗하게 바꿔도 그 복제본에는 옛 커밋이 그대로 남아 있습니다. 포크된 저장소의 히스토리까지 소유자가 강제로 바꿀 권한은 없기 때문에, 이 경우 유일하게 확실한 대응은 역시 키 무효화뿐입니다.
비공개 저장소라고 완전히 안심할 수도 없습니다. 비공개 저장소의 시크릿 스캐닝은 공개 저장소처럼 기본으로 켜져 있지 않고, 별도의 보안 기능을 추가로 적용해야 작동하는 경우가 많습니다. 즉 “비공개니까 괜찮겠지”라는 가정으로 발견이 늦어질 수 있다는 뜻이라, 비공개 저장소를 쓰더라도 키가 커밋에 들어가는 상황 자체를 사전에 막는 쪽이 훨씬 안전합니다.
지금 당장 할 일을 정리하면, 유출된 서비스 콘솔에 들어가 키를 비활성화하고 새 키를 발급받아 .gitignore로 관리하는 작업이 먼저입니다. 히스토리 재작성은 팀 상황을 봐 가며 그다음에 처리해도 늦지 않고, 커밋 전에 git diff --cached로 올라갈 내용을 한 번 더 확인하는 습관을 들이면 같은 상황이 반복되는 걸 줄일 수 있습니다.
