/보안/Emergency Recovery Guide: Accidentally Pushed .env or API Keys to Git
Securitygit시크릿관리

Emergency Recovery Guide: Accidentally Pushed .env or API Keys to Git

Accidentally pushed .env files or API keys to git? This guide walks through fully purging them from history with git filter-repo and BFG, immediately rotating exposed keys, and locking down prevention so it does not happen again.

Emergency Recovery Guide: Accidentally Pushed .env or API Keys to Git

Emergency Recovery Guide: Accidentally Pushed .env or API Keys to Git

Just ran git push and realized your .env or AWS_SECRET_ACCESS_KEY went up with it? Your heart just dropped. What you need right now is not panic — it is first aid you can follow in order. Stick to the sequence below.

Do this first — priority order

  1. (First) Immediately revoke/rotate the exposed keys and passwords — deletion comes after
  2. Completely remove secrets from git history, then force push
  3. Notify collaborators + set up prevention

The order might feel backwards. Key invalidation comes before rewriting history. Here is why.

Trap 1 — git rm --cached plus a new commit will never actually remove it

This is the most common mistake.

Bash
git rm --cached .env
git commit -m "remove .env"
git push

That removes .env from the current file tree. But git permanently stores every change as commit objects. The secret is still frozen in earlier commits. Check for yourself.

Bash
# Past commits still print the full .env contents
git log -p -- .env

# Or check out that commit and the file comes back
git checkout <commit-with-secret> -- .env
cat .env   # the password is sitting there intact

The "deletion commit" actually becomes a billboard saying a secret used to live here — which makes things worse. Bottom line: you do not need a new commit, you need to rewrite history itself.

Completely purge history: git filter-repo vs BFG Repo-Cleaner

Both tools strip files/strings out of history entirely. Before you start, take a backup clone (git clone --mirror).

Bash
# Install
pip install git-filter-repo      # or brew install git-filter-repo

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

# Keep the file, mask only the values
# patterns.txt example: AKIA[0-9A-Z]{16}==>***REMOVED***
git filter-repo --replace-text patterns.txt

Method B — BFG Repo-Cleaner (large repos, simpler)

Bash
# After downloading
java -jar bfg.jar --delete-files .env
# Or replace specific strings (list tokens to remove in secrets.txt)
java -jar bfg.jar --replace-text secrets.txt

# BFG requires you to run post-processing yourself
git reflog expire --expire=now --all
git gc --prune=now --aggressive

filter-repo auto-cleans .git/refs/original, but it is still a good idea to run reflog and gc with either tool.

Itemgit filter-repoBFG Repo-Cleaner
SpeedFastVery fast (large repos)
FlexibilityVery high (paths, regex, etc.)Mostly simple files/strings
DependenciesPythonJava (JVM)
Best forWhen you need fine-grained controlQuickly cleaning huge repositories

force push

Bash
git push --force-with-lease --all
git push --force-with-lease --tags

Use --force-with-lease instead of plain --force. It prevents accidentally overwriting work someone else pushed in the meantime.

The most important step — exposed keys are not "deleted", they are "revoked"

Core principle: assume a bot scanned it the moment you pushed. Public repos get crawled by automated scanners within seconds. Private does not mean safe either. No matter how clean you make history, a key someone may already have copied is worthless unless you invalidate it.

Where to revoke, by service:

  • AWS: IAM → Access keys → Deactivate then Delete the key, issue a new one
  • OpenAI: Platform → API keys → Revoke, then reissue
  • Stripe: Dashboard → Developers → API keys → Roll/Reveal
  • DB passwords: Change immediately and sync app config / secret manager
  • GitHub PAT/OAuth: Settings → Developer settings → delete the token

GitHub Secret Scanning detects some partner tokens (e.g. GitHub Token, AWS, Stripe), notifies the issuer, and sometimes auto-revokes the token. Do not ignore those alerts. Since 2024, Push Protection is on by default for public repos and can block the push itself.

A note from the field: Early in my career I figured a private repo was fine and delayed rotating keys. A token that lingered on a forked internal mirror blew up a month later. "It's private, I'll get to it" is the most expensive assumption you can make.

Impact on collaborators — deleting is not the end

After you rewrite history, other teammates still have the old history (secrets included) locally. Also:

  • Open PRs / cached diffs: GitHub PR pages can cache old commit diffs → close and recreate the PR if needed
  • forks: old objects still live in forked repos

Send the team something like this:

"We rewrote history. Throw away your existing clone and clone fresh. Do not rebase/merge from your local copy. Related keys have already been revoked and reissued."

Wrap-up — a 3-piece prevention kit

Set these up today so you never go through this twice.

Bash
# 1. Add to .gitignore
echo ".env" >> .gitignore
echo "*.pem" >> .gitignore

# 2. Install and register git-secrets
brew install git-secrets
git secrets --install
git secrets --register-aws
git secrets --add 'AKIA[0-9A-Z]{16}'

# 3. Block at push time with a pre-commit hook
pip install pre-commit detect-secrets

Example .pre-commit-config.yaml:

YAML
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets

Add gitleaks or trufflehog in CI for another filter before merge. These days AI coding assistants often auto-fill example keys that then get committed as-is — another reason to keep automated detection on.

Apply-today checklist

  • Rotate/revoke every exposed key
  • Change DB and service passwords
  • Clean history with filter-repo/BFG, then --force-with-lease push
  • Tell the team to re-clone; check forks and PRs
  • Set up .gitignore + git-secrets + pre-commit

FAQ

Q. It's a private repo — do I still have to revoke the keys? A. Yes. Collaborators, forks, CI logs, caches — lots of leak paths, and permissions can change anytime. "I deleted it so we're safe" is a dangerous assumption. Always rotate.

Q. git filter-repo or BFG? A. Need fine-grained path/regex control? filter-repo. Need to quickly clean a multi-GB repo? BFG is easier. After either, finish with reflog expire + gc --prune=now.

Q. I force-pushed but teammates still have the old secrets on their machines. Now what? A. History rewrite only changes the remote. Tell them to delete the old clone and clone again. More importantly: if you already revoked the keys, leftover copies cannot actually do damage.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.