Git 히스토리에서 민감한 데이터를 제거하는 방법 (이번엔 진짜)

발행: (2026년 4월 5일 AM 11:01 GMT+9)
11 분 소요
원문: Dev.to

Source: Dev.to

아니요. 그 API 키, 그 .env 파일, 데이터베이스 자격 증명이 들어 있는 내부 설정— 모두 여전히 존재하고, git 히스토리 안에 편안히 자리 잡고 있습니다. git log와 5분 정도의 호기심만 있으면 누구든지 찾아낼 수 있죠.

약 4년 전, 동료가 우리 스테이징 데이터베이스 비밀번호가 공개 리포지토리에 노출됐다고 알려줬을 때 이 사실을 뼈아프게 배웠습니다. 나는 그 파일을 3개월 전에 삭제했었죠. 의미가 없었습니다. Git은 모든 것을 기억합니다.

파일을 삭제해도 실제로 삭제되지 않는 이유

Git은 콘텐츠 주소 지정 파일 시스템입니다. 각 커밋은 해당 시점에 전체 프로젝트의 스냅샷입니다. git rm secrets.env를 실행하고 커밋하면 해당 파일이 없는 새로운 스냅샷을 만들게 되지만, 이전의 모든 스냅샷에는 여전히 그 파일이 존재합니다.

누구든지 이를 볼 수 있습니다:

# Find all commits that touched a specific file, even deleted ones
git log --all --full-history -- path/to/secrets.env

# Show the contents of that file at a specific commit
git show a1b2c3d:path/to/secrets.env

이는 설계상 의도된 동작입니다. Git의 전체 목적은 데이터를 절대 잃지 않는 것입니다. 이는 소스 코드에는 훌륭하지만, 비밀 정보에는 끔찍합니다.

잘못된 해결책: git revert

사람들이 이 방법을 계속 시도하는 걸 봐요. 그들은 git revert를 실행해서 손상이 되돌려진다고 생각합니다. 그렇지 않죠. revert는 새로운 커밋을 만들어 변경 사항을 되돌리지만, 비밀이 포함된 원래 커밋은 여전히 히스토리 안에 남아 있습니다.

이미 푸시된 커밋에 대해 git commit --amend를 사용하는 경우도 마찬가지입니다. 이전 커밋 객체는 reflog에 남아 있고, 원격 저장소에도 존재할 가능성이 있습니다.

올바른 해결책: git filter-repo

이전 조언은 git filter-branch를 사용하는 것이었지만, 매우 느리고 실수하기 쉽습니다. Git 프로젝트 자체는 이제 git-filter-repo 를 권장합니다.

전체 히스토리에서 파일 완전 삭제

# Install git-filter-repo (requires Python 3.5+)
pip install git-filter-repo

# Clone a fresh copy — filter-repo requires a fresh clone
git clone --mirror https://github.com/you/your-repo.git
cd your-repo.git

# Remove a specific file from all history
git filter-repo --path secrets.env --invert-paths

# Remove a directory from all history
git filter-repo --path config/internal/ --invert-paths

--invert-paths 플래그는 “이 경로 제외 모든 것을 유지한다”는 의미입니다. 이 플래그가 없으면 지정한 경로만 남기고 나머지는 모두 삭제됩니다. 어떻게 아는지는 나중에 물어보세요.

특정 문자열(예: 하드코딩된 API 키) 정리

# Replace a specific string across all history
git filter-repo --replace-text REDACTED

재작성된 히스토리 푸시

git push origin --force --all
git push origin --force --tags

중요: 팀에 알리기

Force‑pushing은 기록을 재작성합니다. 모든 협업자는 로컬 브랜치를 다시 클론하거나 신중하게 리베이스해야 합니다. 그들이 오래된 로컬 복사본을 푸시하면 삭제한 데이터가 바로 다시 돌아옵니다. Force‑push하기 전에 메시지를 보내고 타이밍을 조율하세요.

BFG Repo Cleaner는 어떨까요?

BFG Repo Cleaner 은 특히 Java 기반 도구를 선호한다면 또 다른 확실한 선택입니다. filter-branch 보다 빠르며 일반적인 작업을 위한 인터페이스가 더 간단합니다.

# Remove files by name from all history
java -jar bfg.jar --delete-files secrets.env your-repo.git

# Replace specific text patterns
java -jar bfg.jar --replace-text passwords.txt your-repo.git

# Then clean up and push
cd your-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push origin --force --all

BFG는 의도적으로 최신 커밋을 수정하지 않고, 오직 히스토리만 변경합니다. 이는 안전 장치로, 현재 HEAD가 이미 깨끗하다고 가정합니다.

처음부터 이를 방지하기

역사를 다시 쓰는 일은 고통스럽습니다. 이를 피하는 방법은 다음과 같습니다.

1. 실제로 동작하는 .gitignore 사용

# Environment and secrets
.env
.env.*
*.pem
*.key
*.p12

# Cloud provider configs
.aws/credentials
.gcp-credentials.json

# IDE and OS junk that sometimes contains paths/tokens
.idea/
.vscode/settings.json
.DS_Store

2. Pre‑commit 훅 설정

pre‑commit과 비밀 탐지 플러그인을 사용하면 대부분의 실수 커밋을 사전에 차단할 수 있습니다:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

pre-commit install을 한 번 실행하면, 모든 커밋을 스캔하여 비밀에 해당할 가능성이 있는 고엔트로피 문자열, 알려진 API 키 패턴, 개인 키 등을 찾아냅니다.

3. 환경 변수 또는 비밀 관리자를 사용

이것은 명백해 보이지만, 여전히 누군가가 연결 문자열을 하드코딩한 PR을 보는 경우가 있습니다. 개발 단계에서는 환경 변수를 사용하고, 프로덕션에서는 적절한 비밀 관리자(Vault, 클라우드 제공자의 네이티브 옵션, 혹은 개인 프로젝트의 경우 pass 등)를 활용하세요.

대략적인 원칙: 값이 잘못된 사람에게 넘어가면 손해가 발생할 수 있다면, 그 값은 절대 버전 관리에 포함되지 않아야 합니다. 언제든지.

After the Purge: Rotate Everything

이 단계는 사람들이 종종 건너뛰며, 사실 가장 중요한 단계일 수 있습니다. Git 히스토리를 재작성하면 귀하의 저장소에서 비밀이 제거됩니다. 하지만 다음에서는 제거되지 않습니다:

  • GitHub의 캐시된 복사본 및 모든 포크
  • CI/CD 로그 또는 아티팩트 저장소
  • 이미 비밀을 가져온 로컬 클론
  • 데이터를 캐시했을 가능성이 있는 제3자 서비스

따라서 저장소를 정리한 후에는 다음을 수행하십시오:

  1. Rotate 모든 유출된 자격 증명(API 키, 비밀번호, 인증서 등)을 교체합니다.
  2. Invalidate 노출된 모든 토큰을 무효화합니다.
  3. Audit 로그를 검토하여 유출된 비밀을 사용했을 가능성이 있는 의심스러운 활동을 확인합니다.
  4. Notify 위반이 영향을 미칠 수 있는 이해관계자나 고객에게 알립니다.

TL;DR

  • Git에서 파일을 삭제해도 히스토리에서 사라지지는 않습니다.
  • git revertgit commit --amend는 문제를 해결하지 못합니다.
  • 실제로 비밀 정보를 제거하려면 git filter-repo(또는 BFG)를 사용하세요.
  • 재작성된 히스토리를 강제 푸시하고 팀원들과 반드시 조율하십시오.
  • .gitignore를 제대로 설정하고, 사전 커밋 비밀 스캔 및 적절한 비밀 관리 방식을 도입해 향후 유출을 방지하세요.
  • 정리 작업이 끝난 후에는 노출된 모든 자격 증명을 교체(rotate)하십시오.
# Secrets in Git – What Happens When You Accidentally Push a Credential?

비밀이 유출될 수 있는 곳

  • 레포지토리 포크
  • 누구든지 로컬에 복제한 사본
  • 검색 엔진 캐시
  • Wayback Machine 같은 서비스

비밀이 공개 레포에 한 번이라도 푸시되었다면, 짧은 시간이라도 유출된 것으로 간주하십시오.
자격 증명을 즉시 교체하고, API 키를 재생성하며, 비밀번호를 변경하고, 인증서를 업데이트하십시오.

GitHub 문서는 이 점을 명확히 합니다: 그들은 force‑push가 캐시된 뷰나 복제본에서 데이터를 제거하지 않는다고 명시하고 있습니다. 토큰을 공개 레포에 푸시했다면, 해당 토큰은 사용 불가능한 것으로 간주하십시오.

0 조회
Back to Blog

관련 글

더 보기 »

단계별 Git 명령 가이드

초기 설정 bash git config --global user.name 'Your Name' git config --global user.email 'your@email.com' 새 저장소 초기화 git init 원격 추가…

Cx 개발 로그 — 2026-04-05

Overview 병합 브랜치는 보통 프로젝트에서 가장 흥미로운 부분은 아니지만, 전체 그림을 동기화하는 데 필수적입니다. 오늘은 브랜치에 집중했습니다.

config.py 설정

모든 프로젝트는 같은 방식으로 시작합니다… 몇 가지 값을 hardcode하고, os.getenv 호출을 여기저기 뿌린 뒤, “나중에 정리하겠어”라고 스스로에게 말합니다. 나중은 결코 오지 않습니다. In...