Leaked Secrets in Git History: How to Scan and Remove Them - VibeDoctor 
← All Articles 🔑 Secret Detection Critical

Leaked Secrets in Git History: How to Scan and Remove Them

Removing a secret from code doesn't remove it from Git history. Learn how Gitleaks scans your full commit history for API keys and credentials.

GIT-001 GIT-002 GIT-003 GIT-004 GIT-005

Quick Answer

Deleting a secret from your code and pushing a new commit does not remove it from Git history. Every previous commit is permanently stored and can be read by anyone with repository access - or by the public if the repo is open. You must scan your full commit history, rotate any exposed credentials immediately, and rewrite history to permanently purge the secret.

Git History Is a Permanent Record - Including Your Mistakes

Git's core design principle is immutability: once something is committed, it exists in the object store forever unless you explicitly rewrite history. This is a feature for code - you can always go back. It's a disaster for secrets.

When a developer adds a secret to a file, realizes the mistake, removes it, and commits again, the secret still lives in the earlier commit. Anyone running git log -p, git show <commit-hash>, or browsing the repository's commit history on GitHub can see it in plain text.

GitGuardian's 2024 State of Secrets Sprawl report found that over 12.8 million secrets were detected in public GitHub repositories in 2023 - a 28% increase from the prior year. More concerning: GitGuardian found that 90% of valid secrets remained valid more than 5 days after detection, meaning developers were not rotating credentials after exposure. The time-to-exploit is far shorter: automated scanners find and attempt to use exposed AWS credentials within minutes of a public push.

How Secrets End Up in Git History

The most common paths to a secret ending up in Git history are:

Apiiro's 2024 research found that repositories generated or scaffolded by AI coding assistants had a significantly higher rate of hardcoded credentials than manually written code. The speed of AI generation encourages "get it working first, clean it up later" patterns - and cleanup often never happens.

Scanning Your Full History with Gitleaks

Gitleaks is the most widely used open-source tool for scanning Git repositories for secrets. Unlike a grep on your current files, it walks every commit in your history and checks every diff.

// ❌ BAD - Secret committed in a past file, then deleted
// commit a3f89c2 (2 months ago)
// src/lib/openai.ts

import OpenAI from 'openai';

// Pasted in while testing - "I'll move this to env later"
const client = new OpenAI({
  apiKey: 'sk-proj-EXAMPLE-NOT-REAL-KEY-abc123xyz'
});

// commit b7d12e1 (1 month ago) - "remove hardcoded key"
// The fix ONLY removed the line. The key still lives in commit a3f89c2.

To scan your repository with Gitleaks:

// Running Gitleaks locally (install with: brew install gitleaks)
// Scan the full git history of a repository:
// gitleaks detect --source . --log-opts="--all"

// Output example:
// ○  gitleaks
//     ○  Finding:     apiKey: 'sk-proj-T3BlbkFJQXRsY2x4NWZhZ2xhY2x4'
//     ○  Secret:      sk-proj-T3BlbkFJQXRsY2x4NWZhZ2xhY2x4
//     ○  RuleID:      openai-api-key
//     ○  Entropy:     5.30
//     ○  File:        src/lib/openai.ts
//     ○  Line:        5
//     ○  Commit:      a3f89c2d14f8a3b7e9c1d4f6a8b2e5c7d9f1a3b5
//     ○  Author:      [email protected]
//     ○  Date:        2024-01-15T10:23:41Z

Removing Secrets From Git History

Once you have identified a secret in history, you have two tools available: git filter-repo (the modern, recommended approach) or BFG Repo Cleaner (faster for large repositories). Both rewrite every commit that contained the secret, creating new commit hashes.

Important: after rewriting history, you must force-push and everyone who has cloned the repo must re-clone or re-base. This is a destructive operation. Coordinate with your team before doing it.

// ✅ GOOD - Removing a secret from entire Git history
// Step 1: Install git-filter-repo
// pip install git-filter-repo

// Step 2: Replace the secret with a placeholder in all commits
// git filter-repo --replace-text replacements.txt

// replacements.txt content:
// sk-proj-T3BlbkFJQXRsY2x4NWZhZ2xhY2x4==>REMOVED_SECRET

// Step 3: Force push all branches
// git push origin --force --all
// git push origin --force --tags

// Step 4: Delete and re-clone on all developer machines
// The old commits still exist in GitHub's cache for ~3 months.
// Contact GitHub support to expedite cache purge for critical leaks.

How to Find Leaked Secrets Across Your History

Tool Scans Git History Secret Types CI Integration
Gitleaks Yes - full history 100+ patterns Yes (GitHub Actions)
TruffleHog Yes - full history 700+ detectors Yes
GitHub Secret Scanning Partial (push-time) 100+ partner patterns Built-in (public repos)
GitGuardian Yes - retroactive 350+ detectors Yes
grep / manual search No - current files only Manual patterns only No

How to Fix a Leaked Secret: The Complete Workflow

Scanning is only the first step. Here is the complete remediation workflow once a secret is found in history:

  1. Rotate immediately: Go to the provider's dashboard (AWS IAM, OpenAI, Stripe, GitHub) and generate a new credential. Revoke the old one. Do this before rewriting history - assume the key is already compromised.
  2. Update your environment: Set the new credential in your production environment (Vercel secrets, Railway variables, AWS Secrets Manager) and in your local .env file.
  3. Rewrite history: Use git filter-repo to replace every instance of the old secret across all commits.
  4. Force push: Push the rewritten history to your remote and coordinate team re-clones.
  5. Add prevention: Install Gitleaks as a pre-commit hook so secrets are blocked before they can be committed.

Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase and full Git history for leaked secrets using Gitleaks, flagging the specific file paths, line numbers, and commit hashes where credentials were introduced. Free to sign up.

Prevention: Pre-Commit Hooks and CI Checks

// ✅ GOOD - Gitleaks pre-commit hook (add to .git/hooks/pre-commit)
// Or use via pre-commit framework: https://pre-commit.com

// .pre-commit-config.yaml
// repos:
//   - repo: https://github.com/gitleaks/gitleaks
//     rev: v8.18.0
//     hooks:
//       - id: gitleaks

// GitHub Actions CI check (.github/workflows/secrets-scan.yml):
// - name: Scan for secrets
//   uses: gitleaks/gitleaks-action@v2
//   env:
//     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The Veracode 2024 State of Software Security report found that applications with automated security gates in CI pipelines had 5x faster mean time to remediate vulnerabilities compared to those relying on manual review. A pre-commit hook that takes 2 seconds to run can prevent months of remediation work.

FAQ

If I make my GitHub repo private, are old secrets safe?

No. Making a repo private prevents public access, but anyone who already cloned it has the full history locally. Bots may have already scanned it during any public window. Private repos also get shared with collaborators and services. Treat any exposed secret as compromised regardless of current repo visibility.

Does GitHub's secret scanning protect me?

GitHub's push protection and secret scanning alerts are useful, but they only cover secrets matching known provider patterns and are limited to public repositories on free plans. They also only fire at push time - they won't find secrets that were pushed before the feature was enabled on your repo.

How do I add Gitleaks to GitHub Actions?

Add the gitleaks/gitleaks-action@v2 step to your CI workflow. It scans the diff of every pull request and can be configured to scan the full history on main branch pushes. Failures block merges, preventing secrets from ever reaching the default branch.

What's the difference between Gitleaks and TruffleHog?

Both scan Git history for secrets. Gitleaks uses regex patterns and entropy analysis and is faster. TruffleHog has more detectors (700+ vs 100+) and performs live credential verification against provider APIs to confirm whether found secrets are still valid. For most projects, Gitleaks is sufficient and easier to run locally.

Do I need to rewrite history if I rotate the secret immediately?

Rotating the key makes the exposed credential useless. However, the old key remains visible in history and could cause confusion, trigger false positives in future scans, or be used for phishing. For public repositories, rewriting history and contacting GitHub support to purge the cache is the recommended complete remediation.

Scan your codebase for this issue - free

VibeDoctor checks for GIT-001, GIT-002, GIT-003, GIT-004, GIT-005 and 128 other issues across 15 diagnostic areas.

SCAN MY APP →
← Back to all articles View all 129+ checks →