Tools: Your AWS Credentials Are Still on GitHub Even After You Delete Them

Tools: Your AWS Credentials Are Still on GitHub Even After You Delete Them

The Mistake That Haunts Developers

How Git History Works

Let Me Show You Exactly How Easy It Is

The Bots Are Already Looking

How to Properly Remove Credentials From Git History

Method 1 — git filter-branch (Built into git)

Method 2 — BFG Repo Cleaner (Faster and easier)

Method 3 — GitHub's Built-in Secret Scanning

The Right Way — Prevent It Before It Happens

Solution 1 — Use .gitignore Always

Solution 2 — Use Environment Variables

Solution 3 — git-secrets Pre-Commit Hook

Solution 4 — Use AWS IAM Roles Instead of Access Keys

What To Do RIGHT NOW

If You Find Exposed Credentials — Do This Immediately

Summary

Final Thoughts I thought deleting the file was enough. I was wrong. Here's what actually happens. You're coding late at night. You hardcode your AWS credentials to test something quickly — just this once. You delete the credentials from the file. Push to GitHub. Wrong. Completely wrong. 😰 Your credentials are still on GitHub. Visible to anyone who knows where to look. And automated bots DO know where to look. This happens to developers every single day. Today I'm going to show you exactly why this happens and how to protect yourself completely. Git doesn't just save your current code. Git saves every single version of every file you've ever committed. Think of git like a time machine. Every commit is a snapshot of your entire project at that moment. When you delete credentials and push — git saves: Anyone can travel back to Commit 1 and see your credentials. It's that simple. If someone wanted to find your deleted credentials they would just run: Three commands. Less than 30 seconds. Your "deleted" credentials are right there. And GitHub makes this even easier — anyone can browse your commit history directly in the browser. No commands needed. Here's what makes this terrifying. There are automated bots constantly scanning every public GitHub repository for: These bots find new commits within seconds of being pushed. By the time you realize your mistake and delete the credentials — the bots have already found them. Already saved them. Already started using them. In 2024 a developer pushed AWS credentials to GitHub at 2am. By 4am — $50,000 worth of EC2 instances were running in his account mining cryptocurrency. If you've already pushed credentials — deleting them is NOT enough. You need to rewrite git history. BFG is a faster alternative to git filter-branch. GitHub now automatically scans for exposed secrets and notifies you. Go to your repository → Settings → Security → Secret scanning → Enable GitHub will alert you if it finds credentials in your code. ✅ Fixing exposed credentials is stressful. Preventing it is easy. Create a .gitignore file in every project: Any file listed here will NEVER be committed to git. Ever. ✅ Never put credentials in your code files. Always use environment variables. Set environment variables in terminal: Or use a .env file with python-dotenv: .env is in .gitignore so it never gets pushed. ✅ This is the best solution shared by a reader in the comments of my previous article — and it's brilliant. git-secrets automatically scans your code BEFORE every commit. If it finds credentials — it blocks the commit completely. Once installed — you literally cannot accidentally commit AWS credentials. The commit is blocked before it happens. This is the cheapest insurance you can get. 🔐 The ultimate solution — no access keys at all. When your code runs on EC2, Lambda, or other AWS services — use IAM Roles instead of access keys. IAM Roles provide temporary credentials automatically. Nothing to hardcode. Nothing to accidentally expose. Zero credentials in code. Zero risk of exposure. ✅ Go through this checklist immediately: Even if you think you're safe — check anyway. You might be surprised what you find. Speed matters here. Every minute counts. This article exists because of a comment from a fellow developer on my previous article about IAM mistakes. They mentioned git-secrets as "the cheapest insurance" — and they were absolutely right. The best security lessons come from the community. From real developers who've seen real things go wrong. If you learned something from this — share it with one developer friend. You might save them from a $50,000 AWS bill. 💪 Follow LearnWithPrashik for more practical AWS security and backend development content. Connect with me:

LinkedIn: linkedin.com/in/prashik-besekar

GitHub: github.com/prashikBesekar Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# See all commits -weight: 500;">git log # See what changed in a specific commit -weight: 500;">git show abc123def # Search entire -weight: 500;">git history for keywords -weight: 500;">git log -p | grep -i "aws_access_key" -weight: 500;">git log -p | grep -i "secret" -weight: 500;">git log -p | grep -i "password" # See all commits -weight: 500;">git log # See what changed in a specific commit -weight: 500;">git show abc123def # Search entire -weight: 500;">git history for keywords -weight: 500;">git log -p | grep -i "aws_access_key" -weight: 500;">git log -p | grep -i "secret" -weight: 500;">git log -p | grep -i "password" # See all commits -weight: 500;">git log # See what changed in a specific commit -weight: 500;">git show abc123def # Search entire -weight: 500;">git history for keywords -weight: 500;">git log -p | grep -i "aws_access_key" -weight: 500;">git log -p | grep -i "secret" -weight: 500;">git log -p | grep -i "password" # Remove a specific file from entire -weight: 500;">git history -weight: 500;">git filter-branch --force --index-filter \ "-weight: 500;">git rm --cached --ignore-unmatch path/to/file-with-credentials.py" \ --prune-empty --tag-name-filter cat -- --all # Force push to GitHub -weight: 500;">git push origin --force --all -weight: 500;">git push origin --force --tags # Remove a specific file from entire -weight: 500;">git history -weight: 500;">git filter-branch --force --index-filter \ "-weight: 500;">git rm --cached --ignore-unmatch path/to/file-with-credentials.py" \ --prune-empty --tag-name-filter cat -- --all # Force push to GitHub -weight: 500;">git push origin --force --all -weight: 500;">git push origin --force --tags # Remove a specific file from entire -weight: 500;">git history -weight: 500;">git filter-branch --force --index-filter \ "-weight: 500;">git rm --cached --ignore-unmatch path/to/file-with-credentials.py" \ --prune-empty --tag-name-filter cat -- --all # Force push to GitHub -weight: 500;">git push origin --force --all -weight: 500;">git push origin --force --tags # Install BFG -weight: 500;">brew -weight: 500;">install bfg # Mac # or download bfg.jar from rtyley.github.io/bfg-repo-cleaner/ # Remove all files containing passwords bfg --delete-files id_{dsa,rsa} # Replace specific text in all commits bfg --replace-text passwords.txt # Clean up -weight: 500;">git reflog expire --expire=now --all && -weight: 500;">git gc --prune=now --aggressive -weight: 500;">git push origin --force --all # Install BFG -weight: 500;">brew -weight: 500;">install bfg # Mac # or download bfg.jar from rtyley.github.io/bfg-repo-cleaner/ # Remove all files containing passwords bfg --delete-files id_{dsa,rsa} # Replace specific text in all commits bfg --replace-text passwords.txt # Clean up -weight: 500;">git reflog expire --expire=now --all && -weight: 500;">git gc --prune=now --aggressive -weight: 500;">git push origin --force --all # Install BFG -weight: 500;">brew -weight: 500;">install bfg # Mac # or download bfg.jar from rtyley.github.io/bfg-repo-cleaner/ # Remove all files containing passwords bfg --delete-files id_{dsa,rsa} # Replace specific text in all commits bfg --replace-text passwords.txt # Clean up -weight: 500;">git reflog expire --expire=now --all && -weight: 500;">git gc --prune=now --aggressive -weight: 500;">git push origin --force --all # .gitignore # Environment files .env .env.local .env.development .env.production # AWS credentials .aws/credentials credentials.json # Key files *.pem *.key id_rsa id_dsa # .gitignore # Environment files .env .env.local .env.development .env.production # AWS credentials .aws/credentials credentials.json # Key files *.pem *.key id_rsa id_dsa # .gitignore # Environment files .env .env.local .env.development .env.production # AWS credentials .aws/credentials credentials.json # Key files *.pem *.key id_rsa id_dsa # WRONG ❌ - Never do this aws_access_key = "AKIAIOSFODNN7EXAMPLE" aws_secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" # RIGHT ✅ - Always do this import os aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') # WRONG ❌ - Never do this aws_access_key = "AKIAIOSFODNN7EXAMPLE" aws_secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" # RIGHT ✅ - Always do this import os aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') # WRONG ❌ - Never do this aws_access_key = "AKIAIOSFODNN7EXAMPLE" aws_secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" # RIGHT ✅ - Always do this import os aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY') export AWS_ACCESS_KEY_ID=your_key_here export AWS_SECRET_ACCESS_KEY=your_secret_here export AWS_ACCESS_KEY_ID=your_key_here export AWS_SECRET_ACCESS_KEY=your_secret_here export AWS_ACCESS_KEY_ID=your_key_here export AWS_SECRET_ACCESS_KEY=your_secret_here -weight: 500;">pip -weight: 500;">install python-dotenv -weight: 500;">pip -weight: 500;">install python-dotenv -weight: 500;">pip -weight: 500;">install python-dotenv from dotenv import load_dotenv import os load_dotenv() # Loads .env file aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') from dotenv import load_dotenv import os load_dotenv() # Loads .env file aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') from dotenv import load_dotenv import os load_dotenv() # Loads .env file aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID') AWS_ACCESS_KEY_ID=your_key_here AWS_SECRET_ACCESS_KEY=your_secret_here AWS_DEFAULT_REGION=ap-south-1 AWS_ACCESS_KEY_ID=your_key_here AWS_SECRET_ACCESS_KEY=your_secret_here AWS_DEFAULT_REGION=ap-south-1 AWS_ACCESS_KEY_ID=your_key_here AWS_SECRET_ACCESS_KEY=your_secret_here AWS_DEFAULT_REGION=ap-south-1 # Install -weight: 500;">git-secrets -weight: 500;">brew -weight: 500;">install -weight: 500;">git-secrets # Mac # For Linux -weight: 500;">git clone https://github.com/awslabs/-weight: 500;">git-secrets cd -weight: 500;">git-secrets && -weight: 600;">sudo make -weight: 500;">install # Set up in your repository cd your-project -weight: 500;">git secrets ---weight: 500;">install -weight: 500;">git secrets --register-aws # Now try to commit credentials - it will be blocked! # Install -weight: 500;">git-secrets -weight: 500;">brew -weight: 500;">install -weight: 500;">git-secrets # Mac # For Linux -weight: 500;">git clone https://github.com/awslabs/-weight: 500;">git-secrets cd -weight: 500;">git-secrets && -weight: 600;">sudo make -weight: 500;">install # Set up in your repository cd your-project -weight: 500;">git secrets ---weight: 500;">install -weight: 500;">git secrets --register-aws # Now try to commit credentials - it will be blocked! # Install -weight: 500;">git-secrets -weight: 500;">brew -weight: 500;">install -weight: 500;">git-secrets # Mac # For Linux -weight: 500;">git clone https://github.com/awslabs/-weight: 500;">git-secrets cd -weight: 500;">git-secrets && -weight: 600;">sudo make -weight: 500;">install # Set up in your repository cd your-project -weight: 500;">git secrets ---weight: 500;">install -weight: 500;">git secrets --register-aws # Now try to commit credentials - it will be blocked! # With IAM Role attached to EC2 - no credentials needed at all! import boto3 # boto3 automatically uses the EC2 instance's IAM Role s3 = boto3.client('s3') response = s3.list_buckets() # With IAM Role attached to EC2 - no credentials needed at all! import boto3 # boto3 automatically uses the EC2 instance's IAM Role s3 = boto3.client('s3') response = s3.list_buckets() # With IAM Role attached to EC2 - no credentials needed at all! import boto3 # boto3 automatically uses the EC2 instance's IAM Role s3 = boto3.client('s3') response = s3.list_buckets() ☐ Search your GitHub repos for "aws_access_key" in commit history ☐ Search for "secret_key" in commit history ☐ Add .gitignore to every project ☐ Move all credentials to environment variables ☐ Install -weight: 500;">git-secrets on your machine ☐ Enable GitHub secret scanning on your repos ☐ Rotate any credentials that were ever in -weight: 500;">git history ☐ Search your GitHub repos for "aws_access_key" in commit history ☐ Search for "secret_key" in commit history ☐ Add .gitignore to every project ☐ Move all credentials to environment variables ☐ Install -weight: 500;">git-secrets on your machine ☐ Enable GitHub secret scanning on your repos ☐ Rotate any credentials that were ever in -weight: 500;">git history ☐ Search your GitHub repos for "aws_access_key" in commit history ☐ Search for "secret_key" in commit history ☐ Add .gitignore to every project ☐ Move all credentials to environment variables ☐ Install -weight: 500;">git-secrets on your machine ☐ Enable GitHub secret scanning on your repos ☐ Rotate any credentials that were ever in -weight: 500;">git history - Commit 1 — file WITH credentials ← still here forever - Commit 2 — file WITHOUT credentials - AWS access keys - Secret keys - Private keys - Deactivate the keys immediately — AWS Console → IAM → Users → Security Credentials → Deactivate - Create new access keys — fresh keys, stored safely - Check AWS CloudTrail — see if anyone used your exposed keys - Check your AWS bill — look for unexpected charges - Clean -weight: 500;">git history — use BFG or filter-branch - Report to AWS — if you see unauthorized usage contact AWS Support immediately