Chapter 10-1 — API Key Committed to Public Repo | DevInHyderabad☕ ☕ 20 min read

API Key Committed to Public Repo

Pehle key rotate karo, phir history saf karo. Yeh disaster recovery ka asli test hai.

01The Nightmare: Secrets in Version Control

Committing secrets (API keys, passwords, tokens, private keys) to Git is one of the most dangerous mistakes in software development.

If the repo is PUBLIC on GitHub, automated bots will find your secrets within MINUTES. These bots constantly scan every new commit and public repo for patterns like AWS keys, database URLs, and API tokens.

Even if the repo is PRIVATE, secrets are still exposed to anyone with read access. And if the repo is ever made public later, the secrets are in the history forever.

Simply deleting the file and committing does NOT remove it from history. The secret still exists in previous commits and can be easily accessed with git show.

This is not a theoretical risk — thousands of AWS keys, database passwords, and crypto wallets have been drained because of leaked secrets in Git.

# The classic mistake
echo "AWS_SECRET_KEY=abc123xyz" > .env
git add .env
git commit -m "add environment config"
git push origin main

# Oops! The .env file with the secret
# is now in history.

# WRONG FIX: Just delete the file
rm .env
git add .env
git commit -m "remove .env"
git push origin main

# The file is gone from latest version, BUT:
git log --all --full-history -- .env
# commit abc1234: add environment config
# ← SECRET STILL HERE!

git show abc1234:.env
# AWS_SECRET_KEY=abc123xyz
# ← ANYONE CAN SEE THIS!
02Step 1: Rotate the Key IMMEDIATELY

BEFORE cleaning Git history, you MUST invalidate/rotate the compromised credential.

Treat every committed secret as compromised — even if you delete it from history later, you cannot know if someone already copied it.

Rotation steps:

  • Generate a new key/secret
  • Update your application to use the new key
  • Disable/delete the old key
  • Verify the old key no longer works

For AWS: go to IAM, create new access key, delete old one. For GitHub tokens: go to Settings > Developer settings, delete the token.

This step is MORE IMPORTANT than cleaning Git history. A secret in history is harmless if the secret no longer works.

# IMMEDIATE ACTION: Rotate the credential!

# AWS Example:
# 1. AWS Console → IAM → Users
#    → Security credentials
# 2. Create new access key
# 3. Update app with new key
# 4. Delete old key (starting with abc123xyz)

# GitHub Token Example:
# 1. GitHub → Settings → Developer settings
#    → Personal access tokens
# 2. Delete compromised token
# 3. Generate new token with same permissions

# Database Password Example:
# 1. Connect to database
# 2. ALTER USER admin
#    WITH PASSWORD "new-secure-password";
# 3. Update app connection string

# Verify old key is dead:
aws s3 ls --profile old-key-profile
# > Access Denied ← Good, it no longer works!

# ONLY AFTER ROTATION:
# proceed to clean Git history
03Step 2: Remove from History with git filter-repo

git filter-repo is the modern, recommended tool for rewriting Git history to remove sensitive data. It replaces the deprecated git filter-branch.

It rewrites the repository history, creating new commits that do not contain the secret file.

Installation: pip install git-filter-repo (Python package).

Key commands:

  • git filter-repo --invert-paths --path .env — removes the .env file from ALL commits in history
  • git filter-repo --replace-text <expressions-file> — replaces specific text (like the key value) across all files in history, useful if the secret was in a config file you want to keep

After filter-repo, ALL commit hashes change. Everyone must re-clone.

# Install git-filter-repo
pip install git-filter-repo

# Option 1: Remove entire file from history
git filter-repo --invert-paths --path .env
# Rewriting history... done.
# The .env file no longer exists in ANY commit.

# Option 2: Replace specific text
# (keep file, remove secret)
echo "abc123xyz==>REDACTED" > replacements.txt
git filter-repo --replace-text replacements.txt
# Every occurrence of "abc123xyz" is replaced
# with "REDACTED"
# The file still exists but the secret is gone

# Verify the secret is gone
git log --all --full-history -- .env
# should show nothing (Option 1)
git grep "abc123xyz" $(git rev-list --all)
# should find nothing

# IMPORTANT: filter-repo requires a fresh clone
git clone https://github.com/user/repo.git cleanup-repo
cd cleanup-repo
git filter-repo --invert-paths --path .env
04Step 3: Force Push and Team Coordination

After filter-repo, your local history has completely different commit hashes than the remote.

You MUST force push to update the remote: git push --force --all.

This is one of the rare times force pushing to main is justified.

EVERY team member must take action: they cannot simply pull. They must discard their old clone and re-clone.

If someone has unpushed local commits, they must rebase them onto the new history (which is complex) or cherry-pick them into a fresh clone.

GitHub also has a "Contact GitHub Support" option to purge cached views of the secret, but only do this after rotating the key.

# Force push the cleaned history
git push --force --all
git push --force --tags

# Notify your team IMMEDIATELY:
# "URGENT: I committed a secret to the repo.
#  The key has been rotated.
#  History has been rewritten.
#  You MUST re-clone the repository.
#  Do NOT pull from old clone —
#  it will re-introduce the secret!"

# Team members must:
cd ..
rm -rf old-repo-clone
git clone https://github.com/user/repo.git
cd repo

# If someone has unpushed local work:
# 1. Create patches from old clone
cd old-repo-clone
git format-patch origin/main..HEAD
# 2. Apply patches to new clone
cd ../new-clone
git am *.patch

# Clean up GitHub caches (optional)
# Go to: https://github.com/contact
# Request: "Please purge cached views
# of secret in repo/user/repo"
The #1 rule of leaked secrets: ROTATE FIRST, CLEAN SECOND. Even if you clean Git history instantly, assume the secret was already scraped by a bot. A secret in Git history is only dangerous if it still works. Once rotated, the leaked value becomes useless. Then take your time to properly clean the history.
05Prevention: Stop Secrets Before They're Committed

Prevention is infinitely better than disaster recovery. Use automated tools to catch secrets before they reach the repository.

  • Pre-commit hooks: Use gitleaks, detect-secrets, or talisman to scan staged changes for secrets before every commit.
  • GitHub Secret Scanning: GitHub automatically scans public repos for known secret patterns (AWS keys, GitHub tokens, etc.) and notifies the provider.
  • .gitignore: Always add .env, *.key, *.pem, credentials.json to .gitignore at project start.
  • Environment variables: Never hardcode secrets. Use .env files locally (ignored by Git) and environment variable injection in production.
# Prevention 1: Pre-commit hook with gitleaks
brew install gitleaks  # or: pip install gitleaks

# Add to .git/hooks/pre-commit
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
gitleaks protect --staged
if [ $? -ne 0 ]; then
  echo "SECRET DETECTED! Remove it."
  exit 1
fi
exit 0
EOF
chmod +x .git/hooks/pre-commit

# Prevention 2: Proper .gitignore from day one
cat > .gitignore << 'EOF'
# NEVER commit these
.env
.env.local
*.pem
*.key
credentials.json
service-account.json
id_rsa
EOF

# Prevention 3: Use env vars, not hardcoded values
# BAD:
API_KEY = "abc123xyz"  # in code

# GOOD:
API_KEY = os.environ.get("API_KEY")  # from env

# Prevention 4: GitHub Secret Scanning
# Enable in: repo Settings → Code security

# Prevention 5: Husky for team enforcement
npx husky init
echo "gitleaks protect --staged" > .husky/pre-commit
💡 Pro Tip: Install gitleaks as a pre-commit hook so it automatically scans every commit for secrets. gitleaks protect --staged checks only the files you are about to commit. This catches secrets before they even enter your local history, making the whole problem disappear.

Lo kar liya — Key Points:

  • ✅ Committing secrets to Git exposes them in the repository history, even after deletion from the latest version
  • ✅ Step 1 is ALWAYS to rotate/invalidate the compromised credential before cleaning Git history
  • ✅ git filter-repo is the modern tool to remove sensitive data from history, replacing the deprecated git filter-branch
  • ✅ After filter-repo, you must force push and ALL team members must re-clone from scratch
  • ✅ Prevention is critical: use pre-commit hooks (gitleaks), .gitignore, and environment variables to stop secrets before they reach Git
  • ✅ Bots scan public GitHub repos within minutes — assume any committed secret is immediately compromised
  • ✅ Simply deleting the file and committing does NOT remove it from history — it still exists in previous commits
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase