Git History Is Forever: Why Committed Secrets Never Die
Security First — Part 3 of 30
The $100,000 Lesson Uber Paid So You Don't Have To
In 2016, an Uber engineer committed AWS credentials to a public GitHub repository. An attacker found them, used those credentials to access Uber's AWS infrastructure, and walked out with the personal data of 57 million users and drivers. Uber paid the attackers $100,000 to delete the stolen data and stay quiet — a decision that later led to criminal charges and one of the most embarrassing corporate cover-up stories in tech history.
The engineer probably figured it was a temporary commit. A quick test. They'd clean it up later.
They never got the chance.
That's the thing about git history: it doesn't care about your intentions. It records everything.
Why This Matters More Than Ever for Vibe Coders
If you're using AI tools like Cursor, Claude, or GitHub Copilot to build your projects — and you're pushing that code to GitHub — you need to understand something fundamental about how git works. It's not a file editor. It's a time machine that keeps every version of every file you've ever committed.
And the numbers are getting worse, not better.
According to a March 2026 Security Ledger report, 28.76 million new secrets were exposed in public GitHub commits in 2025 — a 34% increase over the prior year, the largest annual jump ever recorded. Developers using AI coding tools leaked secrets in 3.2% of their commits, more than double the baseline rate of 1.5% for developers not using AI assistance.
Let that sink in. The more you lean on AI to write code faster, the more likely you are to accidentally ship a secret into your git history.
And once it's there? The clock is ticking — but it may never run out. The same report found that 64% of credentials originally identified in 2022 are still valid and exploitable as of early 2026. People don't rotate their keys. The secrets just sit there, silently waiting.
The "I'll Just Delete It" Trap
Here's the scenario I see play out constantly:
- You're building something with AI assistance. The AI generates a code snippet that connects to a database or an API.
- You drop your real credentials in to test it quickly. Works great.
- You commit and push.
- You realize what you did, delete the credentials from the file, and push a new commit.
- You breathe a sigh of relief.
You are not safe.
Git doesn't store files. It stores snapshots of files at every commit. When you delete a secret in a new commit, the old commit — the one with the secret — is still there. Anyone who clones your repo gets the full history. Anyone who runs git log can browse through it. Anyone who knows the commit hash can pull that exact snapshot and read your credentials.
Here's a quick demo. Say you accidentally committed a .env file:
# This is what your history looks like after "deleting" the secret
git log --oneline
a3f9d12 Remove .env file <-- people think the secret is gone
b7c1e08 Add database connection <-- SECRET IS RIGHT HERE
4d2a901 Initial commit
Anyone with access to your repo can run:
git show b7c1e08:.env
# OUTPUT:
# DATABASE_URL=postgres://admin:mypassword123@prod.db.example.com/users
# STRIPE_SECRET_KEY=sk_live_4eC39HqLyjWDarjtT1zdp9Zz
Game over.
It Gets Worse: Even "Deleted" Commits Aren't Gone
In September 2025, security researcher Sharon Brizinov and Truffle Security published findings from a sweeping investigation of GitHub's "oops commits" — commits that developers tried to erase via force-pushing. GitHub retains every public commit as "zero-commit" PushEvents in its archive, even ones you thought you nuked.
Brizinov scanned these dangling commits going back to 2020 and uncovered thousands of live secrets: AWS credentials, MongoDB connection strings, GitHub Personal Access Tokens. One particularly jaw-dropping find: a GitHub PAT with admin permissions over the Istio service mesh repositories — a critical piece of infrastructure used by thousands of companies. The token was swiftly revoked after responsible disclosure, but the point stands: you cannot assume a pushed commit is ever truly gone.
Brizinov earned roughly $25,000 in bug bounty rewards from those findings. The attackers who don't report what they find earn a lot more.
What a Secret Actually Looks Like in Code
Vibe coders often don't realize what counts as a "secret." It's not just passwords. Here's what you should never commit:
# Python example — ALL of these are secrets
OPENAI_API_KEY = "sk-proj-abc123..." # AI service credential
DATABASE_URL = "postgres://user:pass@host/db" # Database connection
AWS_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7M..." # Cloud credentials
STRIPE_SECRET_KEY = "sk_live_..." # Payment processor
GITHUB_TOKEN = "ghp_abc123..." # Code repo access
JWT_SECRET = "my-super-secret-jwt-key" # Auth signing key
// JavaScript/TypeScript — same deal
const config = {
mongoUri: 'mongodb+srv://admin:password@cluster.mongodb.net', // NEVER
twilioAuthToken: 'AC8f2d8e9b1c4a5f6e7d8c9b0a1b2c3d4', // NEVER
sendgridApiKey: 'SG.abc123...', // NEVER
};
Instead, all of these belong in environment variables — values that live outside your code:
import os
from dotenv import load_dotenv
load_dotenv() # Loads from .env file
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # Read from environment
DATABASE_URL = os.getenv("DATABASE_URL")
# Your .env file (which NEVER gets committed)
OPENAI_API_KEY=sk-proj-abc123...
DATABASE_URL=postgres://user:pass@host/db
And your .gitignore file must include:
# .gitignore
.env
.env.local
.env.production
*.key
*_credentials.json
secrets.yaml
config/secrets*
The Scale of the Problem in 2025–2026
To put the AI angle in perspective: GitGuardian's 2025 State of Secrets Sprawl report found 23.8 million secrets leaked on public GitHub repositories in 2024 alone — a 25% jump from the year before. The 2025 numbers pushed that even further, with AI service credentials representing one of the fastest-growing categories.
Of the ten fastest-growing secret types tracked in 2025, eight were tied to AI services. There were over 113,000 leaked API keys for AI provider DeepSeek found in public repositories in a single year.
The pattern is clear: vibe coders are spinning up AI integrations faster than they're learning to secure them. The AI generates code that works. The developer ships it. The credential sits in git history for years.
How to Actually Fix a Leaked Secret
If you've already pushed a secret, here's the correct response — in order:
Step 1: Revoke it immediately. Go to the service dashboard (OpenAI, AWS, Stripe, GitHub, etc.) and invalidate the key right now. Don't wait. Assume it's already been found.
Step 2: Generate a new credential. Create a fresh key/token to replace the compromised one.
Step 3: Clean the history (carefully). Use git filter-repo to rewrite your commit history:
# Install git-filter-repo
pip install git-filter-repo
# Remove a specific file from all history
git filter-repo --path .env --invert-paths
# Or replace a specific secret string across all commits
# Create a file called replacements.txt containing:
# sk-proj-abc123...==>***REMOVED***
git filter-repo --replace-text replacements.txt
# Force-push the cleaned history
git push --force --all
git push --force --tags
Step 4: Store the new secret properly. Use environment variables, a .env file that's in .gitignore, or a secrets manager.
Critical caveat: Rewriting git history after others have cloned your repo is complicated. If collaborators have local copies of the old history, they can still access the secret. This is why not committing it in the first place is orders of magnitude better than cleaning it up after the fact.
Prevention: Your Defense Stack
Here's the layered approach that actually works:
Layer 1: Pre-commit hooks. Stop secrets before they ever hit your git history.
# Install detect-secrets
pip install detect-secrets
# Scan your current codebase for secrets
detect-secrets scan > .secrets.baseline
# Add a pre-commit hook
pip install pre-commit
Add to .pre-commit-config.yaml:
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Layer 2: GitHub push protection. GitHub now automatically scans for known secret patterns and blocks pushes containing them. Enable it in your repo settings under Security → Secret Scanning. It supports hundreds of secret types including AWS, Azure, Stripe, OpenAI, and many more.
Layer 3: Environment variable discipline. Every secret lives in .env. Every .env is in .gitignore. No exceptions.
Layer 4: Periodic history scans. Run gitleaks or trufflehog against your repo periodically to catch anything that slipped through:
# Install and run gitleaks
brew install gitleaks
gitleaks detect --source . --verbose
The AI Coding Specific Risk
Here's something worth calling out explicitly for vibe coders: when you ask an AI to help you connect to an API or database, it will often generate placeholder code that looks like this:
# AI-generated connection code
import openai
client = openai.OpenAI(api_key="YOUR_API_KEY_HERE")
The instinct is to replace YOUR_API_KEY_HERE with your actual key right in the file. Don't. Even if it's "just for testing." Even if you plan to clean it up. The moment you commit it, it's in history.
Instead, always use environment variables from day one:
import os
import openai
from dotenv import load_dotenv
load_dotenv()
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
Set up your .env file. Add it to .gitignore. This takes 60 seconds and saves you from a potentially catastrophic breach.
Action Items Checklist
- Check your existing repos for secrets: run
gitleaks detect --source . --verbosein each project directory - Create a
.gitignorefile that includes.env,*.key, and*credentials*before your next commit - Create a
.envfile for your current project and move any hardcoded credentials there - Install
pre-commitanddetect-secretsin your project - Enable GitHub Secret Scanning and push protection in your repository settings
- If you find a leaked secret in history: revoke immediately, then clean history with
git filter-repo - Tell the AI explicitly: "Use environment variables for any credentials" when generating connection code
- Audit repos you've made public in the past — run a historical scan with TruffleHog
Ask The Guild
Community prompt: Have you ever accidentally committed a secret or credential to a public repo — or discovered someone else did? What happened next? What tools or habits have you adopted to prevent it? Share your war stories (and near-misses) in the comments — the more concrete the scenario, the more the rest of the Guild learns.